diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/AppCreationScripts.md b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/AppCreationScripts.md index 29bb3c0d..6e062df6 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/AppCreationScripts.md +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/AppCreationScripts.md @@ -1,34 +1,37 @@ -# Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts +# Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell ## Overview ### Quick summary -1. On Windows run PowerShell and navigate to the root of the cloned directory +1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory 1. In PowerShell run: + ```PowerShell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force ``` -1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) + +1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. + ```PowerShell - cd .\AppCreationScripts\ - .\Configure.ps1 + cd .\AppCreationScripts\ + .\Configure.ps1 -TenantId "your test tenant's id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" ``` -1. Open the Visual Studio solution and click start ### More details -The following paragraphs: +- [Goal of the provided scripts](#goal-of-the-provided-scripts) + - [Presentation of the scripts](#presentation-of-the-scripts) + - [Usage pattern for tests and DevOps scenarios](#usage-pattern-for-tests-and-DevOps-scenarios) +- [How to use the app creation scripts?](#how-to-use-the-app-creation-scripts) + - [Pre-requisites](#pre-requisites) + - [Run the script and start running](#run-the-script-and-start-running) + - [Four ways to run the script](#four-ways-to-run-the-script) + - [Option 1 (interactive)](#option-1-interactive) + - [Option 2 (Interactive, but create apps in a specified tenant)](#option-3-Interactive-but-create-apps-in-a-specified-tenant) + - [Running the script on Azure Sovereign clouds](#running-the-script-on-Azure-Sovereign-clouds) -- [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. -- Explain the [pre-requisites](#pre-requisites) -- Explain [four ways of running the scripts](#four-ways-to-run-the-script): - - [Interactively](#option-1-interactive) to create the app in your home tenant - - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant - - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) - - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) - -## Goal of the scripts +## Goal of the provided scripts ### Presentation of the scripts @@ -37,92 +40,85 @@ This sample comes with two PowerShell scripts, which automate the creation of th These scripts are: - `Configure.ps1` which: - - creates Azure AD applications and their related objects (permissions, dependencies, secrets), - - changes the configuration files in the C# and JavaScript projects. + - creates Azure AD applications and their related objects (permissions, dependencies, secrets, app roles), + - changes the configuration files in the sample projects. - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Azure AD application it created: - the identifier of the application - the AppId of the application - the url of its registration in the [Azure portal](https://portal.azure.com). -- `Cleanup.ps1` which cleans-up the Azure AD objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset). +- `Cleanup.ps1` which cleans-up the Azure AD objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, `git reset`). ### Usage pattern for tests and DevOps scenarios The `Configure.ps1` will stop if it tries to create an Azure AD application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below. -## How to use the app creation scripts ? +## How to use the app creation scripts? ### Pre-requisites 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) -2. Navigate to the root directory of the project. -3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command: +1. Navigate to the root directory of the project. +1. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command: + ```PowerShell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process ``` -### (Optionally) install AzureAD PowerShell modules -The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: -4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: +### (Optionally) install Microsoft.Graph.Applications PowerShell modules + +The scripts install the required PowerShell module (Microsoft.Graph.Applications) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: - 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). +1. If you have never done it already, in the PowerShell window, install the Microsoft.Graph.Applications PowerShell modules. For this: + + 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select **Run as administrator**). 2. Type: + ```PowerShell - Install-Module AzureAD + Install-Module Microsoft.Graph.Applications ``` or if you cannot be administrator on your machine, run: + ```PowerShell - Install-Module AzureAD -Scope CurrentUser + Install-Module Microsoft.Graph.Applications -Scope CurrentUser ``` ### Run the script and start running -5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, +1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, + ```PowerShell cd AppCreationScripts ``` -6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. -7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. -8. select **Start** for the projects -You're done. this just works! +1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. +1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. +1. select **Start** for the projects + +You're done! -### Four ways to run the script +### Two ways to run the script We advise four ways of running the script: - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, -- non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, -- Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects, -- non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. +- Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects, Here are the details on how to do this. #### Option 1 (interactive) -- Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). +- Just run ``.\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). - The script will be run as the signed-in user and will use the tenant in which the user is defined. Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in. -#### Option 2 (non-interactive) - -When you know the indentity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window - -```PowerShell -$secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force -$mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) -. .\Cleanup.ps1 -Credential $mycreds -. .\Configure.ps1 -Credential $mycreds -``` - -Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. - -#### Option 3 (Interactive, but create apps in a specified tenant) +#### Option 2 (Interactive, but create apps in a specified tenant) if you want to create the apps in a particular tenant, you can use the following option: -- open the [Azure portal](https://portal.azure.com) + +- Open the [Azure portal](https://portal.azure.com) - Select the Azure Active directory you are interested in (in the combo-box below your name on the top right of the browser window) - Find the "Active Directory" object in this tenant - Go to **Properties** and copy the content of the **Directory Id** property @@ -134,14 +130,19 @@ $tenantId = "yourTenantIdGuid" . .\Configure.ps1 -TenantId $tenantId ``` -#### Option 4 (non-interactive, and create apps in a specified tenant) +### Running the script on Azure Sovereign clouds -This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: +All the four options listed above can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`. -```PowerShell -$secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force -$mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) -$tenantId = "yourTenantIdGuid" -. .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId -. .\Configure.ps1 -Credential $mycreds -TenantId $tenantId -``` +The acceptable values for this parameter are: + +- AzureCloud +- AzureChinaCloud +- AzureUSGovernment + +Example: + + ```PowerShell + . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment" + . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment" + ``` diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Cleanup.ps1 b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Cleanup.ps1 index 13acf9e0..f4e36d23 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Cleanup.ps1 +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Cleanup.ps1 @@ -1,70 +1,87 @@ + [CmdletBinding()] param( - [PSCredential] $Credential, [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')] - [string] $tenantId + [string] $tenantId, + [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] + [string] $azureEnvironmentName ) -if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) { - Install-Module "AzureAD" -Scope CurrentUser -} -Import-Module AzureAD -$ErrorActionPreference = "Stop" - Function Cleanup { -<# -.Description -This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script -#> + if (!$azureEnvironmentName) + { + $azureEnvironmentName = "Global" + } + + <# + .Description + This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script + #> # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. - # Login to Azure PowerShell (interactive if credentials are not already provided: - # you'll need to sign-in with creds enabling your to create apps in the tenant) - if (!$Credential -and $TenantId) - { - $creds = Connect-AzureAD -TenantId $tenantId + # Connect to the Microsoft Graph API + Write-Host "Connecting to Microsoft Graph" + if ($tenantId -eq "") { + Connect-MgGraph -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + $tenantId = (Get-MgContext).TenantId } - else - { - if (!$TenantId) - { - $creds = Connect-AzureAD -Credential $Credential - } - else - { - $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential - } - } - - if (!$tenantId) - { - $tenantId = $creds.Tenant.Id + else { + Connect-MgGraph -TenantId $tenantId -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName } - $tenant = Get-AzureADTenantDetail - $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name # Removes the applications - Write-Host "Cleaning-up applications from tenant '$tenantName'" + Write-Host "Cleaning-up applications from tenant '$tenantId'" Write-Host "Removing 'webApp' (WebApp-MultiTenant-v2) if needed" - Get-AzureADApplication -Filter "DisplayName eq 'WebApp-MultiTenant-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } - $apps = Get-AzureADApplication -Filter "DisplayName eq 'WebApp-MultiTenant-v2'" + try + { + Get-MgApplication -Filter "DisplayName eq 'WebApp-MultiTenant-v2'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } + } + catch + { + $message = $_ + Write-Warning $Error[0] + Write-Host "Unable to remove the application 'WebApp-MultiTenant-v2'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red + } + + Write-Host "Making sure there are no more (WebApp-MultiTenant-v2) applications found, will remove if needed..." + $apps = Get-MgApplication -Filter "DisplayName eq 'WebApp-MultiTenant-v2'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain + if ($apps) { - Remove-AzureADApplication -ObjectId $apps.ObjectId + Remove-MgApplication -ApplicationId $apps.Id } foreach ($app in $apps) { - Remove-AzureADApplication -ObjectId $app.ObjectId + Remove-MgApplication -ApplicationId $app.Id -Debug Write-Host "Removed WebApp-MultiTenant-v2.." } + # also remove service principals of this app - Get-AzureADServicePrincipal -filter "DisplayName eq 'WebApp-MultiTenant-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} - + try + { + Get-MgServicePrincipal -filter "DisplayName eq 'WebApp-MultiTenant-v2'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} + } + catch + { + $message = $_ + Write-Warning $Error[0] + Write-Host "Unable to remove ServicePrincipal 'WebApp-MultiTenant-v2'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red + } } -Cleanup -Credential $Credential -tenantId $TenantId \ No newline at end of file +if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { + Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser +} +Import-Module Microsoft.Graph.Applications +$ErrorActionPreference = "Stop" + + +Cleanup -tenantId $tenantId -environment $azureEnvironmentName + +Write-Host "Disconnecting from tenant" +Disconnect-MgGraph diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Configure.ps1 b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Configure.ps1 index b882d824..ad309694 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Configure.ps1 +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/Configure.ps1 @@ -1,45 +1,32 @@ + [CmdletBinding()] param( - [PSCredential] $Credential, [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')] - [string] $tenantId + [string] $tenantId, + [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] + [string] $azureEnvironmentName ) <# This script creates the Azure AD applications needed for this sample and updates the configuration files for the visual Studio projects from the data in the Azure AD applications. - Before running this script you need to install the AzureAD cmdlets as an administrator. - For this: - 1) Run Powershell as an administrator - 2) in the PowerShell window, type: Install-Module AzureAD - + In case you don't have Microsoft.Graph.Applications already installed, the script will automatically install it for the current user + There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. #> -# Create a password that can be used as an application key -Function ComputePassword -{ - $aesManaged = New-Object "System.Security.Cryptography.AesManaged" - $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC - $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros - $aesManaged.BlockSize = 128 - $aesManaged.KeySize = 256 - $aesManaged.GenerateKey() - return [System.Convert]::ToBase64String($aesManaged.Key) -} - # Create an application key # See https://www.sabin.io/blog/adding-an-azure-active-directory-application-and-key-using-powershell/ -Function CreateAppKey([DateTime] $fromDate, [double] $durationInYears, [string]$pw) +Function CreateAppKey([DateTime] $fromDate, [double] $durationInMonths) { - $endDate = $fromDate.AddYears($durationInYears) - $keyId = (New-Guid).ToString(); - $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential - $key.StartDate = $fromDate - $key.EndDate = $endDate - $key.Value = $pw - $key.KeyId = $keyId + $key = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphPasswordCredential + + $key.StartDateTime = $fromDate + $key.EndDateTime = $fromDate.AddMonths($durationInMonths) + $key.KeyId = (New-Guid).ToString() + $key.DisplayName = "app secret" + return $key } @@ -49,19 +36,19 @@ Function CreateAppKey([DateTime] $fromDate, [double] $durationInYears, [string]$ Function AddResourcePermission($requiredAccess, ` $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) { - foreach($permission in $requiredAccesses.Trim().Split("|")) + foreach($permission in $requiredAccesses.Trim().Split("|")) + { + foreach($exposedPermission in $exposedPermissions) { - foreach($exposedPermission in $exposedPermissions) - { - if ($exposedPermission.Value -eq $permission) - { - $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess - $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions - $resourceAccess.Id = $exposedPermission.Id # Read directory data - $requiredAccess.ResourceAccess.Add($resourceAccess) - } - } + if ($exposedPermission.Value -eq $permission) + { + $resourceAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess + $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions + $resourceAccess.Id = $exposedPermission.Id # Read directory data + $requiredAccess.ResourceAccess += $resourceAccess + } } + } } # @@ -76,17 +63,17 @@ Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requ } else { - $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" + $sp = Get-MgServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" } $appid = $sp.AppId - $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess + $requiredAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess $requiredAccess.ResourceAppId = $appid - $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] + $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess] # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: if ($requiredDelegatedPermissions) { - AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" + AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2PermissionScopes -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" } # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application @@ -100,16 +87,14 @@ Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requ Function UpdateLine([string] $line, [string] $value) { - $index = $line.IndexOf('=') - $delimiter = ';' - if ($index -eq -1) - { - $index = $line.IndexOf(':') - $delimiter = ',' - } + $index = $line.IndexOf(':') + $lineEnd = '' + + if($line[$line.Length - 1] -eq ','){ $lineEnd = ',' } + if ($index -ige 0) { - $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter + $line = $line.Substring(0, $index+1) + " " + '"' + $value+ '"' + $lineEnd } return $line } @@ -134,118 +119,132 @@ Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] Set-Content -Path $configFilePath -Value $lines -Force } -Set-Content -Value "" -Path createdApps.html -Add-Content -Value "" -Path createdApps.html - -$ErrorActionPreference = "Stop" - Function ConfigureApplications { -<#.Description - This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the - configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) - so that they are consistent with the Applications parameters -#> - $commonendpoint = "common" - - # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant - # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. - - # Login to Azure PowerShell (interactive if credentials are not already provided: - # you'll need to sign-in with creds enabling your to create apps in the tenant) - if (!$Credential -and $TenantId) - { - $creds = Connect-AzureAD -TenantId $tenantId - } - else - { - if (!$TenantId) - { - $creds = Connect-AzureAD -Credential $Credential - } - else - { - $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential - } - } + $isOpenSSl = 'N' #temporary disable open certificate creation - if (!$tenantId) + <#.Description + This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the + configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) + so that they are consistent with the Applications parameters + #> + + if (!$azureEnvironmentName) { - $tenantId = $creds.Tenant.Id + $azureEnvironmentName = "Global" } - $tenant = Get-AzureADTenantDetail - $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name - - # Get the user running the script to add the user as the app owner - $user = Get-AzureADUser -ObjectId $creds.Account.Id + # Connect to the Microsoft Graph API, non-interactive is not supported for the moment (Oct 2021) + Write-Host "Connecting to Microsoft Graph" + if ($tenantId -eq "") { + Connect-MgGraph -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + $tenantId = (Get-MgContext).TenantId + } + else { + Connect-MgGraph -TenantId $tenantId -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + } + # Create the webApp AAD application Write-Host "Creating the AAD application (WebApp-MultiTenant-v2)" - # Get a 2 years application key for the webApp Application - $pw = ComputePassword + # Get a 6 months application key for the webApp Application $fromDate = [DateTime]::Now; - $key = CreateAppKey -fromDate $fromDate -durationInYears 2 -pw $pw - $webAppAppKey = $pw + $key = CreateAppKey -fromDate $fromDate -durationInMonths 6 + + # create the application - $webAppAadApplication = New-AzureADApplication -DisplayName "WebApp-MultiTenant-v2" ` - -HomePage "https://localhost:44321/" ` - -LogoutUrl "https://localhost:44321/signout-oidc" ` - -ReplyUrls "https://localhost:44321/", "https://localhost:44321/signin-oidc", "https://localhost:44321/Onboarding/ProcessCode" ` - -IdentifierUris "https://$tenantName/WebApp-MultiTenant-v2" ` - -AvailableToOtherTenants $True ` - -PasswordCredentials $key ` - -Oauth2AllowImplicitFlow $true ` - -PublicClient $False - - # create the service principal of the newly created application - $currentAppId = $webAppAadApplication.AppId - $webAppServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} - - # add the user running the script as an app owner if needed - $owner = Get-AzureADApplicationOwner -ObjectId $webAppAadApplication.ObjectId - if ($owner -eq $null) - { - Add-AzureADApplicationOwner -ObjectId $webAppAadApplication.ObjectId -RefObjectId $user.ObjectId + $webAppAadApplication = New-MgApplication -DisplayName "WebApp-MultiTenant-v2" ` + -Web ` + @{ ` + RedirectUris = "https://localhost:44321/", "https://localhost:44321/signin-oidc", "https://localhost:44321/Onboarding/ProcessCode"; ` + HomePageUrl = "https://localhost:44321/"; ` + LogoutUrl = "https://localhost:44321/signout-oidc"; ` + } ` + -SignInAudience AzureADMultipleOrgs ` + #end of command + #add a secret to the application + $pwdCredential = Add-MgApplicationPassword -ApplicationId $webAppAadApplication.Id -PasswordCredential $key + $webAppAppKey = $pwdCredential.SecretText + + $tenantName = (Get-MgApplication -ApplicationId $webAppAadApplication.Id).PublisherDomain + Update-MgApplication -ApplicationId $webAppAadApplication.Id -IdentifierUris @("https://$tenantName/WebApp-MultiTenant-v2") + + # create the service principal of the newly created application + $currentAppId = $webAppAadApplication.AppId + $webAppServicePrincipal = New-MgServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} + + # add the user running the script as an app owner if needed + $owner = Get-MgApplicationOwner -ApplicationId $webAppAadApplication.Id + if ($owner -eq $null) + { + New-MgApplicationOwnerByRef -ApplicationId $webAppAadApplication.Id -BodyParameter = @{"@odata.id" = "htps://graph.microsoft.com/v1.0/directoryObjects/$user.ObjectId"} Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($webAppServicePrincipal.DisplayName)'" - } - - - Write-Host "Done creating the webApp application (WebApp-MultiTenant-v2)" - - # URL of the AAD application in the Azure portal - # Future? $webAppPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.ObjectId+"/isMSAApp/" - $webAppPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.ObjectId+"/isMSAApp/" - Add-Content -Value "" -Path createdApps.html + } + Write-Host "Done creating the webApp application (WebApp-MultiTenant-v2)" - $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] + # URL of the AAD application in the Azure portal + # Future? $webAppPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.Id+"/isMSAApp/" + $webAppPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.Id+"/isMSAApp/" + Add-Content -Value "" -Path createdApps.html + $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess] + + # Add Required Resources Access (from 'webApp' to 'Microsoft Graph') + Write-Host "Getting access from 'webApp' to 'Microsoft Graph'" + $requiredPermission = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` + -requiredDelegatedPermissions "User.Read.All" ` - # Add Required Resources Access (from 'webApp' to 'Microsoft Graph') - Write-Host "Getting access from 'webApp' to 'Microsoft Graph'" - $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` - -requiredDelegatedPermissions "User.Read.All" ` + $requiredResourcesAccess.Add($requiredPermission) + Update-MgApplication -ApplicationId $webAppAadApplication.Id -RequiredResourceAccess $requiredResourcesAccess + Write-Host "Granted permissions." - $requiredResourcesAccess.Add($requiredPermissions) + Write-Host "Successfully registered and configured that app registration for 'WebApp-MultiTenant-v2' at" -ForegroundColor Green + # print the registered app portal URL for any further navigation + $webAppPortalUrl + + # Update config file for 'webApp' + # $configFile = $pwd.Path + "\..\appsettings.json" + $configFile = $(Resolve-Path ($pwd.Path + "\..\appsettings.json")) + + $dictionary = @{ "ClientId" = $webAppAadApplication.AppId;"TenantId" = 'organizations';"Domain" = $tenantName;"ClientSecret" = $webAppAppKey }; - Set-AzureADApplication -ObjectId $webAppAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess - Write-Host "Granted permissions." + Write-Host "Updating the sample config '$configFile' with the following config values" + $dictionary - # Update config file for 'webApp' - $configFile = $pwd.Path + "\..\appsettings.json" - Write-Host "Updating the sample code ($configFile)" - $dictionary = @{ "ClientId" = $webAppAadApplication.AppId;"TenantId" = 'organizations';"Domain" = $tenantName;"ClientSecret" = $webAppAppKey }; - UpdateTextFile -configFilePath $configFile -dictionary $dictionary - - Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
webApp$currentAppIdWebApp-MultiTenant-v2
webApp$currentAppIdWebApp-MultiTenant-v2
" -Path createdApps.html -} + UpdateTextFile -configFilePath $configFile -dictionary $dictionary + if($isOpenSSL -eq 'Y') + { + Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------" + Write-Host "You have generated certificate using OpenSSL so follow below steps: " + Write-Host "Install the certificate on your system from current folder." + Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------" + } + Add-Content -Value "" -Path createdApps.html +} # end of ConfigureApplications function # Pre-requisites -if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { - Install-Module "AzureAD" -Scope CurrentUser +if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { + Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser } -Import-Module AzureAD +Import-Module Microsoft.Graph.Applications + +Set-Content -Value "" -Path createdApps.html +Add-Content -Value "" -Path createdApps.html + +$ErrorActionPreference = "Stop" # Run interactively (will ask you for the tenant ID) -ConfigureApplications -Credential $Credential -tenantId $TenantId \ No newline at end of file + +try +{ + ConfigureApplications -tenantId $tenantId -environment $azureEnvironmentName +} +catch +{ + $message = $_ + Write-Warning $Error[0] + Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red +} +Write-Host "Disconnecting from tenant" +Disconnect-MgGraph \ No newline at end of file diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/sample.json b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/sample.json index acb8036c..777ab3f3 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/sample.json +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/AppCreationScripts/sample.json @@ -1,11 +1,16 @@ { "Sample": { - "Title": "Build a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect", + "Title": "Integrate a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect", "Level": 400, "Client": "ASP.NET Core Web App", "Service": "Microsoft Graph", "RepositoryUrl": "microsoft-identity-platform-aspnetcore-webapp-tutorial", - "Endpoint": "AAD v2.0" + "Endpoint": "Microsoft identity platform", + "platforms": ["aspnetcore"], + "Description":"Protect a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect", + "Languages": ["aspnetcore", "csharp"], + "Products": ["azure-active-directory","ms-graph","microsoft-identity-web"], + "ms.author":["TiagoBrenck"] }, /* @@ -39,7 +44,7 @@ "CodeConfiguration": [ { "App": "webApp", - "SettingKind": "JSon", + "SettingKind": "JSON", "SettingFile": "\\..\\appsettings.json", "Mappings": [ { diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/README.md b/2-WebApp-graph-user/2-3-Multi-Tenant/README.md index 47a0bc68..2bb7f2c1 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/README.md +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/README.md @@ -1,8 +1,9 @@ --- -services: active-directory -platforms: dotnet -endpoint: Microsoft identity platform page_type: sample +client: ASP.NET Core Web App +service: Microsoft Graph +platforms: dotnetcore +endpoint: Microsoft identity platform author: TiagoBrenck level: 400 client: ASP.NET Core Web App @@ -10,15 +11,14 @@ service: Microsoft Graph languages: - CSharp products: - - azure - - azure-active-directory - - dotnet - - CSharp - - office-ms-graph + - azure-active-directory + - ms-graph + - microsoft-identity-web +name: Integrate a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect description: "Protect a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect" --- -# Protect a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect +# Integrate a multi-tenant SaaS web application that calls Microsoft Graph using Azure AD & OpenID Connect > This sample is for Azure AD, not Azure AD B2C. @@ -28,9 +28,8 @@ description: "Protect a multi-tenant SaaS web application that calls Microsoft G - [Overview](#overview) - [Scenario](#scenario) - [How to run this sample](#how-to-run-this-sample) - - [Step 1: Clone or download this repository](#step-1-clone-or-download-this-repository) - - [Step 2: Register the sample application with your Azure Active Directory tenant](#step-2-register-the-sample-application-with-your-azure-active-directory-tenant) - - [Step 3: Configure the sample to use your Azure AD tenant](#step-3-configure-the-sample-to-use-your-azure-ad-tenant) + - [Step 1: Clone or download this repository](#step-1--clone-or-download-this-repository) + - [Step 2: Register the sample application with your Azure Active Directory tenant](#step-2--register-the-sample-application-with-your-azure-active-directory-tenant) - [Step 4: Run the sample](#step-4-run-the-sample) - [About The code](#about-the-code) - [Usage of `/common` endpoint](#usage-of-common-endpoint) @@ -87,14 +86,14 @@ Ideally, you would want to have two Azure AD tenants so you can test all the asp From your shell or command line: -```Shell +```console git clone https://github.com/Azure-Samples/microsoft-identity-platform-aspnetcore-webapp-tutorial.git cd "2-WebApp-graph-user\2-3-Multi-Tenant" ``` -or download and extract the repository .zip file. +or download and extract the repository *.zip* file. -> Given that the name of the sample is quiet long, and so are the names of the referenced NuGet packages, you might want to clone it in a folder close to the root of your hard drive, to avoid path length limitations on Windows. +> :warning: To avoid path length limitations on Windows, we recommend cloning into a directory near the root of your drive. ### Step 2: Register the sample application with your Azure Active Directory tenant @@ -105,85 +104,82 @@ There is one project in this sample. To register it, you can: - **automatically** creates the Azure AD applications and related objects (passwords, permissions, dependencies) for you. Note that this works for Visual Studio only. - modify the Visual Studio projects' configuration files. -
- Expand this section if you want to use this automation: - -1. On Windows, run PowerShell and navigate to the root of the cloned directory -1. In PowerShell run: +
+ Expand this section if you want to use this automation: - ```PowerShell - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force - ``` - -1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. -1. In PowerShell run: + > :warning: If you have never used **Microsoft Graph PowerShell** before, we recommend you go through the [App Creation Scripts Guide](./AppCreationScripts/AppCreationScripts.md) once to ensure that your environment is prepared correctly for this step. + + 1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory + 1. In PowerShell run: - ```PowerShell - cd .\AppCreationScripts\ - .\Configure.ps1 - ``` + ```PowerShell + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force + ``` - > Other ways of running the scripts are described in [App Creation Scripts](./AppCreationScripts/AppCreationScripts.md) - > The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios. + 1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. + 1. For interactive process -in PowerShell, run: -1. Open the Visual Studio solution and click start to run the code. + ```PowerShell + cd .\AppCreationScripts\ + .\Configure.ps1 -TenantId "[Optional] - your tenant id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" + ``` -
+ > Other ways of running the scripts are described in [App Creation Scripts guide](./AppCreationScripts/AppCreationScripts.md). The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios. -Follow the steps below to manually walk through the steps to register and configure the applications. +
-### Step 3: Configure the sample to use your Azure AD tenant +#### Choose the Azure AD tenant where you want to create your applications -As a first step you'll need to: +To manually register the apps, as a first step you'll need to: -1. Sign in to the [Azure portal](https://portal.azure.com) using either a work or school account or a personal Microsoft account. -1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory**. - Change your portal session to the desired Azure AD tenant. +1. Sign in to the [Azure portal](https://portal.azure.com). +1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory** to change your portal session to the desired Azure AD tenant. -#### Register the web app (WebApp-MultiTenant-v2) +#### Register the webApp app (WebApp-MultiTenant-v2) -1. Navigate to the Microsoft identity platform for developers [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) page. -1. Click **New registration** on top. +1. Navigate to the [Azure portal](https://portal.azure.com) and select the **Azure Active Directory** service. +1. Select the **App Registrations** blade on the left, then select **New registration**. 1. In the **Register an application page** that appears, enter your application's registration information: - - In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `WebApp-MultiTenant-v2`. - - Change **Supported account types** to **Accounts in any organizational directory**. - > Note that there are more than one redirect URIs used in this sample. You'll need to add them from the **Authentication** tab later after the app has been created successfully. -1. Click on the **Register** button in bottom to create the application. -1. In the app's registration screen, find the **Application (client) ID** value and record it for use later. You'll need it to configure the configuration file(s) later in your code. -1. In the app's registration screen, click on the **Authentication** blade in the left. - - In the Redirect URIs section, select **Web** in the drop down and enter the following redirect URIs. - - `https://localhost:44321/` - - `https://localhost:44321/signin-oidc` - - `https://localhost:44321/Onboarding/ProcessCode` - - In the **Advanced settings** section, set **Logout URL** to `https://localhost:44321/signout-oidc`. - - In the **Advanced settings** | **Implicit grant** section, check **ID tokens** as this sample requires - the [ID Token](https://docs.microsoft.com/azure/active-directory/develop/id-tokens) to be enabled to - sign-in the user. - -1. Click the **Save** button on top to save the changes. -1. In the app's registration screen, click on the **Certificates & secrets** blade in the left to open the page where we can generate secrets and upload certificates. -1. In the **Client secrets** section, click on **New client secret**: - - Type a key description (for instance `app secret`), - - Select one of the available key durations (**In 1 year**, **In 2 years**, or **Never Expires**) as per your security concerns. - - The generated key value will be displayed when you click the **Add** button. Copy the generated value for use in the steps later. - - You'll need this key later in your code's configuration files. This key value will not be displayed again, and is not retrievable by any other means, so make sure to note it from the Azure portal before navigating to any other screen or blade. -1. In the app's registration screen, click on the **API permissions** blade in the left to open the page where we add access to the Apis that your application needs. - - Click the **Add a permission** button and then, - - Ensure that the **Microsoft APIs** tab is selected. - - In the *Commonly used Microsoft APIs* section, click on **Microsoft Graph** - - In the **Delegated permissions** section, select the **User.Read.All** in the list. Use the search box if necessary. - - Click on the **Add permissions** button in the bottom. - -##### Configure the project (WebApp-OpenIDConnect-DotNet) to use your app registration - -Open the project in your IDE (like Visual Studio) to configure the code. ->In the steps below, "ClientID" is the same as "Application ID" or "AppId". - -1. Open the `appsettings.json` file -1. Find the app key `ClientId` and replace the existing value with the application ID (clientId) of the `WebApp-MultiTenant-v2` application copied from the Azure portal. -1. Find the app key `TenantId` and replace the existing value with `organizations`. -1. Find the app key `Domain` and replace the existing value with your Azure AD tenant name. -1. Find the app key `ClientSecret` and replace the existing value with the key you saved during the creation of the `WebApp-MultiTenant-v2` app, in the Azure portal. + 1. In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `WebApp-MultiTenant-v2`. + 1. Under **Supported account types**, select **Accounts in any organizational directory** + 1. Select **Register** to create the application. +1. In the **Overview** blade, find and note the **Application (client) ID**. You use this value in your app's configuration file(s) later in your code. +1. In the app's registration screen, select the **Authentication** blade to the left. +1. If you don't have a platform added, select **Add a platform** and select the **Web** option. + 1. In the **Redirect URI** section enter the following redirect URIs: + 1. `https://localhost:44321/` + 1. `https://localhost:44321/signin-oidc` + 1. `https://localhost:44321/Onboarding/ProcessCode` + 1. In the **Front-channel logout URL** section, set it to `https://localhost:44321/signout-oidc`. + 1. Click **Save** to save your changes. +1. In the app's registration screen, select the **Certificates & secrets** blade in the left to open the page where you can generate secrets and upload certificates. +1. In the **Client secrets** section, select **New client secret**: + 1. Type a key description (for instance `app secret`). + 1. Select one of the available key durations (**6 months**, **12 months** or **Custom**) as per your security posture. + 1. The generated key value will be displayed when you select the **Add** button. Copy and save the generated value for use in later steps. + 1. You'll need this key later in your code's configuration files. This key value will not be displayed again, and is not retrievable by any other means, so make sure to note it from the Azure portal before navigating to any other screen or blade. + > :bulb: For enhanced security, instead of using client secrets, consider [using certificates](./README-use-certificate.md) and [Azure KeyVault](https://azure.microsoft.com/services/key-vault/#product-overview). + +1. Since this app signs-in users, we will now proceed to select **delegated permissions**, which is is required by apps signing-in users. + 1. In the app's registration screen, select the **API permissions** blade in the left to open the page where we add access to the APIs that your application needs: + 1. Select the **Add a permission** button and then, + 1. Ensure that the **Microsoft APIs** tab is selected. + 1. In the *Commonly used Microsoft APIs* section, select **Microsoft Graph** + * Since this app signs-in users, we will now proceed to select **delegated permissions**, which is is requested by apps when signing-in users. + 1. In the **Delegated permissions** section, select the **User.Read.All** in the list. Use the search box if necessary. + 1. Select the **Add permissions** button at the bottom. + +##### Configure the webApp app (WebApp-MultiTenant-v2) to use your app registration + +Open the project in your IDE (like Visual Studio or Visual Studio Code) to configure the code. + +> In the steps below, "ClientID" is the same as "Application ID" or "AppId". + +1. Open the `appsettings.json` file. +1. Find the key `ClientId` and replace the existing value with the application ID (clientId) of `WebApp-MultiTenant-v2` app copied from the Azure portal. +1. Find the key `TenantId` and replace the existing value with 'organizations'. +1. Find the key `Domain` and replace the existing value with your Azure AD tenant domain, ex. `contoso.onmicrosoft.com`. +1. Find the key `ClientSecret` and replace the existing value with the key you saved during the creation of `WebApp-MultiTenant-v2` copied from the Azure portal. ### Step 4: Run the sample diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/Startup.cs b/2-WebApp-graph-user/2-3-Multi-Tenant/Startup.cs index ea95b0e6..e2939a6c 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/Startup.cs +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/Startup.cs @@ -137,8 +137,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( - name: "default", + name: "Index", pattern: "{controller=Home}/{action=Index}/{id?}"); + + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller}/{action}/{id?}"); endpoints.MapRazorPages(); }); } diff --git a/2-WebApp-graph-user/2-3-Multi-Tenant/WebApp-OpenIDConnect-DotNet.csproj b/2-WebApp-graph-user/2-3-Multi-Tenant/WebApp-OpenIDConnect-DotNet.csproj index e3309bdb..ba4098f2 100644 --- a/2-WebApp-graph-user/2-3-Multi-Tenant/WebApp-OpenIDConnect-DotNet.csproj +++ b/2-WebApp-graph-user/2-3-Multi-Tenant/WebApp-OpenIDConnect-DotNet.csproj @@ -19,10 +19,14 @@ - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/3-WebApp-multi-APIs/AppCreationScripts/AppCreationScripts.md b/3-WebApp-multi-APIs/AppCreationScripts/AppCreationScripts.md index 18dae89f..6e062df6 100644 --- a/3-WebApp-multi-APIs/AppCreationScripts/AppCreationScripts.md +++ b/3-WebApp-multi-APIs/AppCreationScripts/AppCreationScripts.md @@ -1,34 +1,37 @@ -# Registering the Azure Active Directory applications and updating the configuration files for this sample using PowerShell scripts +# Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell ## Overview ### Quick summary -1. On Windows run PowerShell and navigate to the root of the cloned directory +1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory 1. In PowerShell run: + ```PowerShell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force ``` -1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) + +1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. + ```PowerShell - cd .\AppCreationScripts\ - .\Configure.ps1 + cd .\AppCreationScripts\ + .\Configure.ps1 -TenantId "your test tenant's id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" ``` -1. Open the Visual Studio solution and click start ### More details -The following paragraphs: +- [Goal of the provided scripts](#goal-of-the-provided-scripts) + - [Presentation of the scripts](#presentation-of-the-scripts) + - [Usage pattern for tests and DevOps scenarios](#usage-pattern-for-tests-and-DevOps-scenarios) +- [How to use the app creation scripts?](#how-to-use-the-app-creation-scripts) + - [Pre-requisites](#pre-requisites) + - [Run the script and start running](#run-the-script-and-start-running) + - [Four ways to run the script](#four-ways-to-run-the-script) + - [Option 1 (interactive)](#option-1-interactive) + - [Option 2 (Interactive, but create apps in a specified tenant)](#option-3-Interactive-but-create-apps-in-a-specified-tenant) + - [Running the script on Azure Sovereign clouds](#running-the-script-on-Azure-Sovereign-clouds) -- [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. -- Explain the [pre-requisites](#pre-requisites) -- Explain [four ways of running the scripts](#four-ways-to-run-the-script): - - [Interactively](#option-1-interactive) to create the app in your home tenant - - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant - - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) - - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) - -## Goal of the scripts +## Goal of the provided scripts ### Presentation of the scripts @@ -37,92 +40,85 @@ This sample comes with two PowerShell scripts, which automate the creation of th These scripts are: - `Configure.ps1` which: - - creates Azure AD applications and their related objects (permissions, dependencies, secrets), - - changes the configuration files in the C# and JavaScript projects. + - creates Azure AD applications and their related objects (permissions, dependencies, secrets, app roles), + - changes the configuration files in the sample projects. - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Azure AD application it created: - the identifier of the application - the AppId of the application - the url of its registration in the [Azure portal](https://portal.azure.com). -- `Cleanup.ps1` which cleans-up the Azure AD objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset). +- `Cleanup.ps1` which cleans-up the Azure AD objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, `git reset`). ### Usage pattern for tests and DevOps scenarios The `Configure.ps1` will stop if it tries to create an Azure AD application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below. -## How to use the app creation scripts ? +## How to use the app creation scripts? ### Pre-requisites 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) -2. Navigate to the root directory of the project. -3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command: +1. Navigate to the root directory of the project. +1. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command: + ```PowerShell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process ``` -### (Optionally) install AzureAD PowerShell modules -The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: -4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: +### (Optionally) install Microsoft.Graph.Applications PowerShell modules + +The scripts install the required PowerShell module (Microsoft.Graph.Applications) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: - 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). +1. If you have never done it already, in the PowerShell window, install the Microsoft.Graph.Applications PowerShell modules. For this: + + 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select **Run as administrator**). 2. Type: + ```PowerShell - Install-Module AzureAD + Install-Module Microsoft.Graph.Applications ``` or if you cannot be administrator on your machine, run: + ```PowerShell - Install-Module AzureAD -Scope CurrentUser + Install-Module Microsoft.Graph.Applications -Scope CurrentUser ``` ### Run the script and start running -5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, +1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, + ```PowerShell cd AppCreationScripts ``` -6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. -7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. -8. select **Start** for the projects -You're done. this just works! +1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. +1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. +1. select **Start** for the projects + +You're done! -### Four ways to run the script +### Two ways to run the script We advise four ways of running the script: - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, -- non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, -- Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects, -- non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. +- Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects, Here are the details on how to do this. #### Option 1 (interactive) -- Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). +- Just run ``.\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). - The script will be run as the signed-in user and will use the tenant in which the user is defined. Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in. -#### Option 2 (non-interactive) - -When you know the indentity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window - -```PowerShell -$secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force -$mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) -. .\Cleanup.ps1 -Credential $mycreds -. .\Configure.ps1 -Credential $mycreds -``` - -Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. - -#### Option 3 (Interactive, but create apps in a specified tenant) +#### Option 2 (Interactive, but create apps in a specified tenant) if you want to create the apps in a particular tenant, you can use the following option: -- open the [Azure portal](https://portal.azure.com) + +- Open the [Azure portal](https://portal.azure.com) - Select the Azure Active directory you are interested in (in the combo-box below your name on the top right of the browser window) - Find the "Active Directory" object in this tenant - Go to **Properties** and copy the content of the **Directory Id** property @@ -134,14 +130,19 @@ $tenantId = "yourTenantIdGuid" . .\Configure.ps1 -TenantId $tenantId ``` -#### Option 4 (non-interactive, and create apps in a specified tenant) +### Running the script on Azure Sovereign clouds -This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: +All the four options listed above can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`. -```PowerShell -$secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force -$mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) -$tenantId = "yourTenantIdGuid" -. .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId -. .\Configure.ps1 -Credential $mycreds -TenantId $tenantId -``` +The acceptable values for this parameter are: + +- AzureCloud +- AzureChinaCloud +- AzureUSGovernment + +Example: + + ```PowerShell + . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment" + . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment" + ``` diff --git a/3-WebApp-multi-APIs/AppCreationScripts/Cleanup.ps1 b/3-WebApp-multi-APIs/AppCreationScripts/Cleanup.ps1 index f8b94804..73b01cd8 100644 --- a/3-WebApp-multi-APIs/AppCreationScripts/Cleanup.ps1 +++ b/3-WebApp-multi-APIs/AppCreationScripts/Cleanup.ps1 @@ -1,62 +1,87 @@ + [CmdletBinding()] param( - [PSCredential] $Credential, [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')] - [string] $tenantId + [string] $tenantId, + [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] + [string] $azureEnvironmentName ) -if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { - Install-Module "AzureAD" -Scope CurrentUser -} -Import-Module AzureAD -$ErrorActionPreference = 'Stop' - Function Cleanup { -<# -.Description -This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script -#> + if (!$azureEnvironmentName) + { + $azureEnvironmentName = "Global" + } + + <# + .Description + This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script + #> # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. - # Login to Azure PowerShell (interactive if credentials are not already provided: - # you'll need to sign-in with creds enabling your to create apps in the tenant) - if (!$Credential -and $TenantId) + # Connect to the Microsoft Graph API + Write-Host "Connecting to Microsoft Graph" + if ($tenantId -eq "") { + Connect-MgGraph -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + $tenantId = (Get-MgContext).TenantId + } + else { + Connect-MgGraph -TenantId $tenantId -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + } + + # Removes the applications + Write-Host "Cleaning-up applications from tenant '$tenantId'" + + Write-Host "Removing 'webApp' (Azure_Api_WebApp) if needed" + try { - $creds = Connect-AzureAD -TenantId $tenantId + Get-MgApplication -Filter "DisplayName eq 'Azure_Api_WebApp'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } } - else + catch { - if (!$TenantId) - { - $creds = Connect-AzureAD -Credential $Credential - } - else - { - $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential - } + $message = $_ + Write-Warning $Error[0] + Write-Host "Unable to remove the application 'Azure_Api_WebApp'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red } - if (!$tenantId) + Write-Host "Making sure there are no more (Azure_Api_WebApp) applications found, will remove if needed..." + $apps = Get-MgApplication -Filter "DisplayName eq 'Azure_Api_WebApp'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain + + if ($apps) { - $tenantId = $creds.Tenant.Id + Remove-MgApplication -ApplicationId $apps.Id } - $tenant = Get-AzureADTenantDetail - $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name - - # Removes the applications - Write-Host "Cleaning-up applications from tenant '$tenantName'" - Write-Host "Removing 'webApp' (WebApp) if needed" - $app=Get-AzureADApplication -Filter "DisplayName eq 'WebApp'" + foreach ($app in $apps) + { + Remove-MgApplication -ApplicationId $app.Id -Debug + Write-Host "Removed Azure_Api_WebApp.." + } - if ($app) + # also remove service principals of this app + try { - Remove-AzureADApplication -ObjectId $app.ObjectId - Write-Host "Removed WebApp." + Get-MgServicePrincipal -filter "DisplayName eq 'Azure_Api_WebApp'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} } + catch + { + $message = $_ + Write-Warning $Error[0] + Write-Host "Unable to remove ServicePrincipal 'Azure_Api_WebApp'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red } +} + +if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { + Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser +} +Import-Module Microsoft.Graph.Applications +$ErrorActionPreference = "Stop" + + +Cleanup -tenantId $tenantId -environment $azureEnvironmentName -Cleanup -Credential $Credential -tenantId $TenantId +Write-Host "Disconnecting from tenant" +Disconnect-MgGraph diff --git a/3-WebApp-multi-APIs/AppCreationScripts/Configure.ps1 b/3-WebApp-multi-APIs/AppCreationScripts/Configure.ps1 index 1018ef7f..e38f2520 100644 --- a/3-WebApp-multi-APIs/AppCreationScripts/Configure.ps1 +++ b/3-WebApp-multi-APIs/AppCreationScripts/Configure.ps1 @@ -1,45 +1,32 @@ + [CmdletBinding()] param( - [PSCredential] $Credential, [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')] - [string] $tenantId + [string] $tenantId, + [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] + [string] $azureEnvironmentName ) <# This script creates the Azure AD applications needed for this sample and updates the configuration files for the visual Studio projects from the data in the Azure AD applications. - Before running this script you need to install the AzureAD cmdlets as an administrator. - For this: - 1) Run Powershell as an administrator - 2) in the PowerShell window, type: Install-Module AzureAD - + In case you don't have Microsoft.Graph.Applications already installed, the script will automatically install it for the current user + There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. #> -# Create a password that can be used as an application key -Function ComputePassword -{ - $aesManaged = New-Object "System.Security.Cryptography.AesManaged" - $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC - $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros - $aesManaged.BlockSize = 128 - $aesManaged.KeySize = 256 - $aesManaged.GenerateKey() - return [System.Convert]::ToBase64String($aesManaged.Key) -} - # Create an application key # See https://www.sabin.io/blog/adding-an-azure-active-directory-application-and-key-using-powershell/ -Function CreateAppKey([DateTime] $fromDate, [double] $durationInYears, [string]$pw) +Function CreateAppKey([DateTime] $fromDate, [double] $durationInMonths) { - $endDate = $fromDate.AddYears($durationInYears) - $keyId = (New-Guid).ToString(); - $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential - $key.StartDate = $fromDate - $key.EndDate = $endDate - $key.Value = $pw - $key.KeyId = $keyId + $key = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphPasswordCredential + + $key.StartDateTime = $fromDate + $key.EndDateTime = $fromDate.AddMonths($durationInMonths) + $key.KeyId = (New-Guid).ToString() + $key.DisplayName = "app secret" + return $key } @@ -49,23 +36,23 @@ Function CreateAppKey([DateTime] $fromDate, [double] $durationInYears, [string]$ Function AddResourcePermission($requiredAccess, ` $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) { - foreach($permission in $requiredAccesses.Trim().Split("|")) + foreach($permission in $requiredAccesses.Trim().Split("|")) + { + foreach($exposedPermission in $exposedPermissions) { - foreach($exposedPermission in $exposedPermissions) - { - if ($exposedPermission.Value -eq $permission) - { - $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess - $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions - $resourceAccess.Id = $exposedPermission.Id # Read directory data - $requiredAccess.ResourceAccess.Add($resourceAccess) - } - } + if ($exposedPermission.Value -eq $permission) + { + $resourceAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess + $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions + $resourceAccess.Id = $exposedPermission.Id # Read directory data + $requiredAccess.ResourceAccess += $resourceAccess + } } + } } # -# Exemple: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" +# Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) { @@ -76,17 +63,17 @@ Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requ } else { - $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" + $sp = Get-MgServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" } $appid = $sp.AppId - $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess + $requiredAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess $requiredAccess.ResourceAppId = $appid - $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] + $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess] # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: if ($requiredDelegatedPermissions) { - AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" + AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2PermissionScopes -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" } # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application @@ -100,16 +87,14 @@ Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requ Function UpdateLine([string] $line, [string] $value) { - $index = $line.IndexOf('=') - $delimiter = ';' - if ($index -eq -1) - { - $index = $line.IndexOf(':') - $delimiter = ',' - } + $index = $line.IndexOf(':') + $lineEnd = '' + + if($line[$line.Length - 1] -eq ','){ $lineEnd = ',' } + if ($index -ige 0) { - $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter + $line = $line.Substring(0, $index+1) + " " + '"' + $value+ '"' + $lineEnd } return $line } @@ -134,127 +119,146 @@ Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] Set-Content -Path $configFilePath -Value $lines -Force } -Set-Content -Value "
ApplicationAppIdUrl in the Azure portal
" -Path createdApps.html -Add-Content -Value "" -Path createdApps.html - Function ConfigureApplications { -<#.Description - This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the - configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) - so that they are consistent with the Applications parameters -#> - - $commonendpoint = "common" + $isOpenSSl = 'N' #temporary disable open certificate creation - # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant - # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. - - # Login to Azure PowerShell (interactive if credentials are not already provided: - # you'll need to sign-in with creds enabling your to create apps in the tenant) - if (!$Credential -and $TenantId) - { - $creds = Connect-AzureAD -TenantId $tenantId - } - else + <#.Description + This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the + configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) + so that they are consistent with the Applications parameters + #> + + if (!$azureEnvironmentName) { - if (!$TenantId) - { - $creds = Connect-AzureAD -Credential $Credential - } - else - { - $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential - } + $azureEnvironmentName = "Global" } - if (!$tenantId) - { - $tenantId = $creds.Tenant.Id + # Connect to the Microsoft Graph API, non-interactive is not supported for the moment (Oct 2021) + Write-Host "Connecting to Microsoft Graph" + if ($tenantId -eq "") { + Connect-MgGraph -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + $tenantId = (Get-MgContext).TenantId } - - $tenant = Get-AzureADTenantDetail - $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name - - # Get the user running the script - $user = Get-AzureADUser -ObjectId $creds.Account.Id + else { + Connect-MgGraph -TenantId $tenantId -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName + } + # Create the webApp AAD application - Write-Host "Creating the AAD application (WebApp)" - # Get a 2 years application key for the webApp Application - $pw = ComputePassword + Write-Host "Creating the AAD application (Azure_Api_WebApp)" + # Get a 6 months application key for the webApp Application $fromDate = [DateTime]::Now; - $key = CreateAppKey -fromDate $fromDate -durationInYears 2 -pw $pw - $webAppAppKey = $pw - $webAppAadApplication = New-AzureADApplication -DisplayName "WebApp" ` - -HomePage "https://localhost:44321/" ` - -LogoutUrl "https://localhost:44321/signout-oidc" ` - -ReplyUrls "https://localhost:44321/", "https://localhost:44321/signin-oidc" ` - -IdentifierUris "https://$tenantName/WebApp" ` - -AvailableToOtherTenants $True ` - -PasswordCredentials $key ` - -Oauth2AllowImplicitFlow $true ` - -PublicClient $False - - $currentAppId = $webAppAadApplication.AppId - $webAppServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} - - # add the user running the script as an app owner if needed - $owner = Get-AzureADApplicationOwner -ObjectId $webAppAadApplication.ObjectId - if ($owner -eq $null) - { - Add-AzureADApplicationOwner -ObjectId $webAppAadApplication.ObjectId -RefObjectId $user.ObjectId + $key = CreateAppKey -fromDate $fromDate -durationInMonths 6 + + + # create the application + $webAppAadApplication = New-MgApplication -DisplayName "Azure_Api_WebApp" ` + -Web ` + @{ ` + RedirectUris = "https://localhost:44321/", "https://localhost:44321/signin-oidc"; ` + HomePageUrl = "https://localhost:44321/"; ` + LogoutUrl = "https://localhost:44321/signout-oidc"; ` + } ` + -SignInAudience AzureADMyOrg ` + #end of command + #add a secret to the application + $pwdCredential = Add-MgApplicationPassword -ApplicationId $webAppAadApplication.Id -PasswordCredential $key + $webAppAppKey = $pwdCredential.SecretText + + $tenantName = (Get-MgApplication -ApplicationId $webAppAadApplication.Id).PublisherDomain + Update-MgApplication -ApplicationId $webAppAadApplication.Id -IdentifierUris @("https://$tenantName/Azure_Api_WebApp") + + # create the service principal of the newly created application + $currentAppId = $webAppAadApplication.AppId + $webAppServicePrincipal = New-MgServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} + + # add the user running the script as an app owner if needed + $owner = Get-MgApplicationOwner -ApplicationId $webAppAadApplication.Id + if ($owner -eq $null) + { + New-MgApplicationOwnerByRef -ApplicationId $webAppAadApplication.Id -BodyParameter = @{"@odata.id" = "htps://graph.microsoft.com/v1.0/directoryObjects/$user.ObjectId"} Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($webAppServicePrincipal.DisplayName)'" - } - - Write-Host "Done creating the webApp application (WebApp)" + } + Write-Host "Done creating the webApp application (Azure_Api_WebApp)" - # URL of the AAD application in the Azure portal - # Future? $webAppPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.ObjectId+"/isMSAApp/" - $webAppPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.ObjectId+"/isMSAApp/" - Add-Content -Value "" -Path createdApps.html + # URL of the AAD application in the Azure portal + # Future? $webAppPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.Id+"/isMSAApp/" + $webAppPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$webAppAadApplication.AppId+"/objectId/"+$webAppAadApplication.Id+"/isMSAApp/" + Add-Content -Value "" -Path createdApps.html + $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess] + + # Add Required Resources Access (from 'webApp' to 'Microsoft Graph') + Write-Host "Getting access from 'webApp' to 'Microsoft Graph'" + $requiredPermission = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` + -requiredDelegatedPermissions "User.Read" ` - $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] + $requiredResourcesAccess.Add($requiredPermission) + + # Add Required Resources Access (from 'webApp' to 'Windows Azure Service Management API') + Write-Host "Getting access from 'webApp' to 'Windows Azure Service Management API'" + $requiredPermission = GetRequiredPermissions -applicationDisplayName "Windows Azure Service Management API" ` + -requiredDelegatedPermissions "user_impersonation" ` - # Add Required Resources Access (from 'webApp' to 'Microsoft Graph') - Write-Host "Getting access from 'webApp' to 'Microsoft Graph'" - $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` - -requiredDelegatedPermissions "User.Read" ` + $requiredResourcesAccess.Add($requiredPermission) + + # Add Required Resources Access (from 'webApp' to 'Azure Storage') + Write-Host "Getting access from 'webApp' to 'Azure Storage'" + $requiredPermission = GetRequiredPermissions -applicationDisplayName "Azure Storage" ` + -requiredDelegatedPermissions "user_impersonation" ` - $requiredResourcesAccess.Add($requiredPermissions) + $requiredResourcesAccess.Add($requiredPermission) + Update-MgApplication -ApplicationId $webAppAadApplication.Id -RequiredResourceAccess $requiredResourcesAccess + Write-Host "Granted permissions." - # Add Required Resources Access (from 'webApp' to 'Windows Azure Service Management API') - Write-Host "Getting access from 'webApp' to 'Windows Azure Service Management API'" - $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Windows Azure Service Management API" ` - -requiredDelegatedPermissions "user_impersonation" ` + Write-Host "Successfully registered and configured that app registration for 'Azure_Api_WebApp' at" -ForegroundColor Green - $requiredResourcesAccess.Add($requiredPermissions) + # print the registered app portal URL for any further navigation + $webAppPortalUrl + + # Update config file for 'webApp' + # $configFile = $pwd.Path + "\..\appsettings.json" + $configFile = $(Resolve-Path ($pwd.Path + "\..\appsettings.json")) + + $dictionary = @{ "ClientId" = $webAppAadApplication.AppId;"TenantId" = $tenantId;"Domain" = $tenantName;"ClientSecret" = $webAppAppKey }; - # Add Required Resources Access (from 'webApp' to 'Azure Storage') - Write-Host "Getting access from 'webApp' to 'Azure Storage'" - $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Azure Storage" ` - -requiredDelegatedPermissions "user_impersonation" ` + Write-Host "Updating the sample config '$configFile' with the following config values" + $dictionary - $requiredResourcesAccess.Add($requiredPermissions) + UpdateTextFile -configFilePath $configFile -dictionary $dictionary + if($isOpenSSL -eq 'Y') + { + Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------" + Write-Host "You have generated certificate using OpenSSL so follow below steps: " + Write-Host "Install the certificate on your system from current folder." + Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------" + } + Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
webApp$currentAppIdWebApp
webApp$currentAppIdAzure_Api_WebApp
" -Path createdApps.html +} # end of ConfigureApplications function +# Pre-requisites +if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { + Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser +} - Set-AzureADApplication -ObjectId $webAppAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess - Write-Host "Granted permissions." +Import-Module Microsoft.Graph.Applications - # Update config file for 'webApp' - $configFile = $pwd.Path + "\..\appsettings.json" - Write-Host "Updating the sample code ($configFile)" - $dictionary = @{ "ClientId" = $webAppAadApplication.AppId;"TenantId" = $tenantId;"Domain" = $tenantName;"ClientSecret" = $webAppAppKey }; - UpdateTextFile -configFilePath $configFile -dictionary $dictionary - - Add-Content -Value "" -Path createdApps.html -} +Set-Content -Value "" -Path createdApps.html +Add-Content -Value "" -Path createdApps.html -# Pre-requisites -if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { - Install-Module "AzureAD" -Scope CurrentUser -} -Import-Module AzureAD +$ErrorActionPreference = "Stop" # Run interactively (will ask you for the tenant ID) -ConfigureApplications -Credential $Credential -tenantId $TenantId \ No newline at end of file + +try +{ + ConfigureApplications -tenantId $tenantId -environment $azureEnvironmentName +} +catch +{ + $message = $_ + Write-Warning $Error[0] + Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red +} +Write-Host "Disconnecting from tenant" +Disconnect-MgGraph \ No newline at end of file diff --git a/3-WebApp-multi-APIs/AppCreationScripts/sample.json b/3-WebApp-multi-APIs/AppCreationScripts/sample.json index 0b1a910e..74ad7b6f 100644 --- a/3-WebApp-multi-APIs/AppCreationScripts/sample.json +++ b/3-WebApp-multi-APIs/AppCreationScripts/sample.json @@ -1,11 +1,17 @@ { "Sample": { - "Title": "An ASP.NET Core 2.x Web App which lets sign-in users with work and school or Microsoft personal accounts (and calls Microsoft Graph)", + "Title": "An ASP.NET Core Web App which sign-in users with work and school or Microsoft personal accounts and calls Azure REST API and Azure Storage", "Level": 200, - "Client": "ASP.NET Core 2.x Web App", - "Service": "Microsoft Graph", + "Client": "ASP.NET Core Web App", + "Service": "Azure REST Api", + "Author":"jmprieur", + "Platform":"aspnetcore", "RepositoryUrl": "active-directory-aspnetcore-webapp-openidconnect-v2", - "Endpoint": "AAD v2.0" + "Endpoint": "Microsoft identity platform", + "Description":"An ASP.NET Core Web App which sign-in users with work and school or Microsoft personal accounts and calls Azure REST API and Azure Storage", + "Languages": ["aspnetcore", "charp"], + "Products": ["azure-active-directory","microsoft-identity-web","azure-resource-manager","azure-resource-graph","ms-graph"], + "ms.author":["jmprieur"] }, /* @@ -14,7 +20,7 @@ "AADApps": [ { "Id": "webApp", - "Name": "WebApp", + "Name": "Azure_Api_WebApp", "Kind": "WebApp", "HomePage": "https://localhost:44321/", "ReplyUrls": "https://localhost:44321/, https://localhost:44321/signin-oidc", @@ -46,7 +52,7 @@ "CodeConfiguration": [ { "App": "webApp", - "SettingKind": "JSon", + "SettingKind": "JSON", "SettingFile": "\\..\\appsettings.json", "Mappings": [ { diff --git a/3-WebApp-multi-APIs/Controllers/HomeController.cs b/3-WebApp-multi-APIs/Controllers/HomeController.cs index dfbaa554..e4a8886f 100644 --- a/3-WebApp-multi-APIs/Controllers/HomeController.cs +++ b/3-WebApp-multi-APIs/Controllers/HomeController.cs @@ -1,138 +1,138 @@ -using Azure.Storage.Blobs; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Identity.Client; -using Microsoft.Identity.Web; -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using WebApp_OpenIDConnect_DotNet.Models; -using WebApp_OpenIDConnect_DotNet.Services.Arm; -using WebApp_OpenIDConnect_DotNet.Services.GraphOperations; - -namespace WebApp_OpenIDConnect_DotNet.Controllers -{ - [Authorize] - public class HomeController : Controller - { - readonly ITokenAcquisition tokenAcquisition; - private readonly IGraphApiOperations graphApiOperations; - private readonly IArmOperations armOperations; - private readonly IArmOperationsWithImplicitAuth armOperationsWithImplicitAuth; - - public HomeController(ITokenAcquisition tokenAcquisition, - IGraphApiOperations graphApiOperations, - IArmOperations armOperations, - IArmOperationsWithImplicitAuth armOperationsWithImplicitAuth) - { - this.tokenAcquisition = tokenAcquisition; - this.graphApiOperations = graphApiOperations; - this.armOperations = armOperations; - this.armOperationsWithImplicitAuth = armOperationsWithImplicitAuth; - } - - public IActionResult Index() - { - return View(); - } - - [AuthorizeForScopes(Scopes = new[] { WebApp_OpenIDConnect_DotNet.Infrastructure.Constants.ScopeUserRead })] - public async Task Profile() - { - var accessToken = - await tokenAcquisition.GetAccessTokenForUserAsync(new[] { WebApp_OpenIDConnect_DotNet.Infrastructure.Constants.ScopeUserRead }); - - var me = await graphApiOperations.GetUserInformation(accessToken); - var photo = await graphApiOperations.GetPhotoAsBase64Async(accessToken); - - ViewData["Me"] = me; - ViewData["Photo"] = photo; - - return View(); - } - - // Requires that the app has added the Azure Service Management / user_impersonation scope, and that - // the admin tenant does not require admin consent for ARM. - [AuthorizeForScopes(Scopes = new[] { "https://management.core.windows.net/user_impersonation", "user.read", "directory.read.all" })] - public async Task Tenants() - { - var accessToken = - await tokenAcquisition.GetAccessTokenForUserAsync(new[] { $"{ArmApiOperationService.ArmResource}user_impersonation" }); - - var tenantIds = await armOperations.EnumerateTenantsIdsAccessibleByUser(accessToken); - /* - var tenantsIdsAndNames = await graphApiOperations.EnumerateTenantsIdAndNameAccessibleByUser(tenantIds, - async tenantId => { return await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "Directory.Read.All" }, tenantId); }); - */ - ViewData["tenants"] = tenantIds; - - return View(); - } - - // Requires that the app has added the Azure Service Management / user_impersonation scope, and that - // the admin tenant does not require admin consent for ARM. - [AuthorizeForScopes(Scopes = new[] { "https://management.core.windows.net/user_impersonation" })] - public async Task TenantsWithImplicitAuth() - { - var tenantIds = await armOperationsWithImplicitAuth.EnumerateTenantsIds(); - /* - var tenantsIdsAndNames = await graphApiOperations.EnumerateTenantsIdAndNameAccessibleByUser(tenantIds, - async tenantId => { return await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "Directory.Read.All" }, tenantId); }); - */ - ViewData["tenants"] = tenantIds; - - return View(nameof(Tenants)); +using Azure.Storage.Blobs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Identity.Client; +using Microsoft.Identity.Web; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using WebApp_OpenIDConnect_DotNet.Models; +using WebApp_OpenIDConnect_DotNet.Services.Arm; +using WebApp_OpenIDConnect_DotNet.Services.GraphOperations; + +namespace WebApp_OpenIDConnect_DotNet.Controllers +{ + [Authorize] + public class HomeController : Controller + { + readonly ITokenAcquisition tokenAcquisition; + private readonly IGraphApiOperations graphApiOperations; + private readonly IArmOperations armOperations; + private readonly IArmOperationsWithImplicitAuth armOperationsWithImplicitAuth; + + public HomeController(ITokenAcquisition tokenAcquisition, + IGraphApiOperations graphApiOperations, + IArmOperations armOperations, + IArmOperationsWithImplicitAuth armOperationsWithImplicitAuth) + { + this.tokenAcquisition = tokenAcquisition; + this.graphApiOperations = graphApiOperations; + this.armOperations = armOperations; + this.armOperationsWithImplicitAuth = armOperationsWithImplicitAuth; + } + + public IActionResult Index() + { + return View(); } - [AuthorizeForScopes(Scopes = new[] { "https://storage.azure.com/user_impersonation" })] - public async Task Blob() - { - string message = "Blob failed to create"; - // replace the URL below with your storage account URL - Uri blobUri = new Uri("https://blobstorageazuread.blob.core.windows.net/sample-container/Blob1.txt"); - BlobClient blobClient = new BlobClient(blobUri, new TokenAcquisitionTokenCredential(tokenAcquisition)); - - string blobContents = "Blob created by Azure AD authenticated user."; - byte[] byteArray = Encoding.ASCII.GetBytes(blobContents); - using (MemoryStream stream = new MemoryStream(byteArray)) - { - try - { - await blobClient.UploadAsync(stream); - message = "Blob successfully created"; - } - catch (MicrosoftIdentityWebChallengeUserException ex) - { - throw ex; - } - catch (MsalUiRequiredException ex) - { - throw ex; - } - catch (Exception ex) - { - try - { - message += $". Reason - {((Azure.RequestFailedException)ex).ErrorCode}"; - } - catch (Exception) - { - message += $". Reason - {ex.Message}"; - } - } - } - - ViewData["Message"] = message; - return View(); - } - - [AllowAnonymous] - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } - } -} + [AuthorizeForScopes(Scopes = new[] { WebApp_OpenIDConnect_DotNet.Infrastructure.Constants.ScopeUserRead })] + public async Task Profile() + { + var accessToken = + await tokenAcquisition.GetAccessTokenForUserAsync(new[] { WebApp_OpenIDConnect_DotNet.Infrastructure.Constants.ScopeUserRead }); + + var me = await graphApiOperations.GetUserInformation(accessToken); + var photo = await graphApiOperations.GetPhotoAsBase64Async(accessToken); + + ViewData["Me"] = me; + ViewData["Photo"] = photo; + + return View(); + } + + // Requires that the app has added the Azure Service Management / user_impersonation scope, and that + // the admin tenant does not require admin consent for ARM. + [AuthorizeForScopes(Scopes = new[] { "https://management.core.windows.net/user_impersonation", "user.read", "directory.read.all" })] + public async Task Tenants() + { + var accessToken = + await tokenAcquisition.GetAccessTokenForUserAsync(new[] { $"{ArmApiOperationService.ArmResource}user_impersonation" }); + + var tenantIds = await armOperations.EnumerateTenantsIdsAccessibleByUser(accessToken); + /* + var tenantsIdsAndNames = await graphApiOperations.EnumerateTenantsIdAndNameAccessibleByUser(tenantIds, + async tenantId => { return await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "Directory.Read.All" }, tenantId); }); + */ + ViewData["tenants"] = tenantIds; + + return View(); + } + + // Requires that the app has added the Azure Service Management / user_impersonation scope, and that + // the admin tenant does not require admin consent for ARM. + [AuthorizeForScopes(Scopes = new[] { "https://management.core.windows.net/user_impersonation" })] + public async Task TenantsWithImplicitAuth() + { + var tenantIds = await armOperationsWithImplicitAuth.EnumerateTenantsIds(); + /* + var tenantsIdsAndNames = await graphApiOperations.EnumerateTenantsIdAndNameAccessibleByUser(tenantIds, + async tenantId => { return await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "Directory.Read.All" }, tenantId); }); + */ + ViewData["tenants"] = tenantIds; + + return View(nameof(Tenants)); + } + + [AuthorizeForScopes(Scopes = new[] { "https://storage.azure.com/user_impersonation" })] + public async Task Blob() + { + string message = "Blob failed to create"; + // replace the URL below with your storage account URL + Uri blobUri = new Uri("https://blobstorageazuread.blob.core.windows.net/sample-container/Blob1.txt"); + BlobClient blobClient = new BlobClient(blobUri, new TokenAcquisitionTokenCredential(tokenAcquisition)); + + string blobContents = "Blob created by Azure AD authenticated user."; + byte[] byteArray = Encoding.ASCII.GetBytes(blobContents); + using (MemoryStream stream = new MemoryStream(byteArray)) + { + try + { + await blobClient.UploadAsync(stream); + message = "Blob successfully created"; + } + catch (MicrosoftIdentityWebChallengeUserException ex) + { + throw ex; + } + catch (MsalUiRequiredException ex) + { + throw ex; + } + catch (Exception ex) + { + try + { + message += $". Reason - {((Azure.RequestFailedException)ex).ErrorCode}"; + } + catch (Exception) + { + message += $". Reason - {ex.Message}"; + } + } + } + + ViewData["Message"] = message; + return View(); + } + + [AllowAnonymous] + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + } +} diff --git a/3-WebApp-multi-APIs/README.md b/3-WebApp-multi-APIs/README.md index 0bd1c6e7..48b3f811 100644 --- a/3-WebApp-multi-APIs/README.md +++ b/3-WebApp-multi-APIs/README.md @@ -1,13 +1,26 @@ --- -services: active-directory -platforms: dotnet -author: jmprieur -level: 200 +page_type: sample +services: ms-identity client: ASP.NET Core Web App -service: Microsoft Graph +service: Azure REST Api +level: 200 +languages: + - aspnetcore + - charp +products: + - azure-active-directory + - microsoft-identity-web + - azure-resource-manager + - azure-resource-graph + - ms-graph +platform: aspnetcore endpoint: Microsoft identity platform +urlFragment: active-directory-aspnetcore-webapp-openidconnect-v2 +name: An ASP.NET Core Web App which sign-in users with work and school or Microsoft personal accounts and calls Azure REST API and Azure Storage +description: An ASP.NET Core Web App which sign-in users with work and school or Microsoft personal accounts and calls Azure REST API and Azure Storage --- -# ASP.NET Core Web API calling Azure REST API and Azure Storage + +# An ASP.NET Core Web App which sign-in users with work and school or Microsoft personal accounts and calls Azure REST API and Azure Storage [![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/AAD%20Samples/.NET%20client%20samples/ASP.NET%20Core%20Web%20App%20tutorial)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=819) diff --git a/3-WebApp-multi-APIs/Startup.cs b/3-WebApp-multi-APIs/Startup.cs index 29aa9b38..ba620764 100644 --- a/3-WebApp-multi-APIs/Startup.cs +++ b/3-WebApp-multi-APIs/Startup.cs @@ -86,9 +86,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( - name: "default", + name: "Index", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); + + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller}/{action}/{id?}"); + endpoints.MapRazorPages(); }); } }
ApplicationAppIdUrl in the Azure portal