|
| 1 | +## Deployment |
| 2 | + |
| 3 | +### Deploying web app to Azure App Services |
| 4 | + |
| 5 | +There is one web app in this sample. To deploy it to **Azure App Services**, you'll need to: |
| 6 | + |
| 7 | +- create an **Azure App Service** |
| 8 | +- publish the projects to the **App Services**, and |
| 9 | +- update its client(s) to call the website instead of the local environment. |
| 10 | + |
| 11 | +#### Publish your files |
| 12 | + |
| 13 | +##### Publish using Visual Studio |
| 14 | + |
| 15 | +Follow the link to [Publish with Visual Studio](https://docs.microsoft.com/visualstudio/deployment/quickstart-deploy-to-azure). |
| 16 | + |
| 17 | +##### Publish using Visual Studio Code |
| 18 | + |
| 19 | +1. Open an instance of Visual Studio code set to the `WebApp-OpenIDConnect-DotNet-graph-v2` project folder. |
| 20 | +1. Install the VS Code extension [Azure App Service](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureappservice). |
| 21 | +1. Using the extension you just installed, sign in to **Azure App Service** using your Azure AD account. |
| 22 | +1. Choose `Terminal > New Terminal` from the VS Code menu to open a new terminal window in the project directory. |
| 23 | +1. Run the following command |
| 24 | + |
| 25 | + ```console |
| 26 | + dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release |
| 27 | + ``` |
| 28 | + |
| 29 | +1. A `publish` folder is created within the following folder: `bin/Release/netcoreapp3.1/`. |
| 30 | +1. From the VS Code file explorer, right-click on the **publish** folder and select **Deploy to Web App**. |
| 31 | +1. Select **Create New Web App**. |
| 32 | +1. Enter a unique name for the app, for example, `WebApp-OpenIDConnect-DotNet-graph-v2`. If you chose `example-domain` for your app name, your app's domain name will be `https://example-domain.azurewebsites.net`. |
| 33 | +1. Select **Windows** as the OS. Press Enter. |
| 34 | +1. Select **.NET Core 3.1 (LTS)** as runtime stack. |
| 35 | +1. Select `Free` or any other option for your pricing tier. |
| 36 | + |
| 37 | +#### Update the Azure AD app registration (WebApp-OpenIDConnect-DotNet-graph-v2) |
| 38 | + |
| 39 | +1. Navigate back to to the [Azure portal](https://portal.azure.com). |
| 40 | +1. Go to the **Azure Active Directory** section, and then select **App registrations**. |
| 41 | +1. In the resulting screen, select the `WebApp-OpenIDConnect-DotNet-graph-v2` application. |
| 42 | +1. In the app's registration screen, select **Authentication** in the menu. |
| 43 | + - In the **Redirect URIs** section, update both of the reply URLs to match the site URL of your Azure deployment. Using the following examples as a guide, **replace** the text `example-domain` with the app name you created while deploying, for example: |
| 44 | + - `https://example-domain.azurewebsites.net/` |
| 45 | + - `https://example-domain.azurewebsites.net/signin-oidc` |
| 46 | +1. Update the **Front-channel logout URL** fields with the address of your service, for example `https://example-domain.azurewebsites.net`. |
| 47 | + |
| 48 | +> :warning: If your app is using *in-memory* storage, **Azure App Services** will spin down your web site if it is inactive, and any records that your app was keeping will emptied. In addition, if you increase the instance count of your website, requests will be distributed among the instances. Your app's records, therefore, will not be the same on each instance. |
| 49 | + |
| 50 | +### Enabling your code to get secrets from Key Vault using Managed Identity |
| 51 | + |
| 52 | +One of the uber principals of security and **Zero Trust** is to place credentials out of your code and use in a manner that allows for credentials to be replaced or rotated without incurring a downtime. |
| 53 | + |
| 54 | +To achieve this we'd place our application's credentials in [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) and access it via [managed Identities for Azure resources](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview). |
| 55 | + |
| 56 | +We will follow the steps broadly outlined in the guide: [Use Key Vault from App Service with Azure Managed Identity](https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/blob/master/README.md) |
| 57 | + |
| 58 | +#### Set up your Managed Identity |
| 59 | + |
| 60 | +1. Navigate to [Azure portal](https://portal.azure.com) and select the **Azure App Service**. |
| 61 | +1. Find and select the App Service you've created previously. |
| 62 | +1. On App Service portal, select **Identity**. |
| 63 | +1. Within the **System assigned** tab, switch **Status** to **On**. Click **Save**. |
| 64 | +1. Record the **Object Id** that will appear, as you will need it in the next step. |
| 65 | + |
| 66 | +For more information, see [Add a system-assigned identity](https://docs.microsoft.com/azure/app-service/overview-managed-identity?tabs=dotnet#add-a-system-assigned-identity) |
| 67 | + |
| 68 | +#### Set up your Key vault |
| 69 | + |
| 70 | +Before starting here, make sure: |
| 71 | + |
| 72 | +- You have an [Azure Subscription](https://azure.microsoft.com/free/). |
| 73 | +- You have a working and deployed application as an Azure App Service following the steps listed at [Deploying web app to Azure App Services](#deploying-web-app-to-azure-app-services) above. |
| 74 | +- Follow the guide to [create an Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/quick-create-portal). |
| 75 | + |
| 76 | +##### Upload your secret to KeyVault |
| 77 | + |
| 78 | +1. Navigate to your new key vault in the Azure portal. |
| 79 | +1. On the Key Vault settings pages, select **Secrets**. |
| 80 | +1. Click on **Generate/Import**. |
| 81 | +1. On the **Create a secret** screen choose the following values: |
| 82 | + - **Upload options**: Manual. |
| 83 | + - **Name**: Type a name for the secret. The secret name must be unique within a Key Vault. For example, `myClientSecret` |
| 84 | + - **Value**: Copy and paste the value for the `ClientSecret` property (without quotes!) from your `appsettings.json` file. |
| 85 | + - Leave the other values to their defaults. Click **Create**. |
| 86 | + |
| 87 | +##### Provide the managed identity access to Key Vault |
| 88 | + |
| 89 | +1. Navigate to your Key Vault in the portal. |
| 90 | +1. Select **Overview** > **Access policies**. |
| 91 | +1. Click on **Add Access Policy**. |
| 92 | +1. In the input box for **Secret permissions**, select **Get**. |
| 93 | +1. Click on **Select Principal**, add the **system-assigned managed identity** that you have created in the [steps before](#set-up-your-managed-identity). You can use the **Object Id** you have recorded previously to search for it. |
| 94 | +1. Click on **OK** to add the new Access Policy, then click **Save** to save the Access Policy. |
| 95 | + |
| 96 | +#### Modify your code to connect to Key Vault |
| 97 | + |
| 98 | +1. In the `appsettings.json` file, find and delete the `ClientSecret` property and its value. |
| 99 | +1. In the `Properties\launchSettings.json` file, find the string `ENTER_YOUR_KEY_VAULT_URI` and replace it with the URI of your Key Vault, for example: `https://example-vault.vault.azure.net/` |
| 100 | +1. Add the `Azure.Identity` NuGet package to the solution. This sample project has already added this package. |
| 101 | +1. Add the following directives to your `startup.cs`. |
| 102 | + </p> |
| 103 | + :information_source: this has been already added in the sample project. |
| 104 | + |
| 105 | +```CSharp |
| 106 | +using Azure; |
| 107 | +using Azure.Identity; |
| 108 | +using Azure.Security.KeyVault.Secrets; |
| 109 | +``` |
| 110 | + |
| 111 | +5. In your `Startup.cs` file, you must create a `GetSecretFromKeyVault` method. This method sets up the Azure Key Vault client and returns the secret that is required. |
| 112 | + </p> |
| 113 | + :information_source: this has already been added in the sample project. |
| 114 | + |
| 115 | +```CSharp |
| 116 | + private string GetSecretFromKeyVault(string tenantId, string secretName) |
| 117 | + { |
| 118 | + // this should point to your vault's URI, like https://<yourkeyvault>.vault.azure.net/ |
| 119 | + string uri = Environment.GetEnvironmentVariable("KEY_VAULT_URI"); |
| 120 | + DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); |
| 121 | + |
| 122 | + // Specify the tenant ID to use the dev credentials when running the app locally |
| 123 | + options.VisualStudioTenantId = tenantId; |
| 124 | + options.SharedTokenCacheTenantId = tenantId; |
| 125 | + SecretClient client = new SecretClient(new Uri(uri), new DefaultAzureCredential(options)); |
| 126 | + |
| 127 | + // The secret name, for example if the full url to the secret is https://<yourkeyvault>.vault.azure.net/secrets/Graph-App-Secret |
| 128 | + Response<KeyVaultSecret> secret = client.GetSecretAsync(secretName).Result; |
| 129 | + |
| 130 | + return secret.Value.Value; |
| 131 | + } |
| 132 | +``` |
| 133 | + |
| 134 | +6. In your `Startup.cs` file, find the `ConfigureServices` method. Add the following code to call the GetSecretFromKeyVault method, right after `services.AddAuthentication`. |
| 135 | + </p> |
| 136 | + :information_source: In the sample project, this code is present but commented out by default. Uncomment it. |
| 137 | + </p> |
| 138 | + :warning: Replace the string `ENTER_YOUR_SECRET_NAME_HERE` with the name of the client secret you entered into Azure Key Vault, for example `myClientSecret`. |
| 139 | + |
| 140 | +```CSharp |
| 141 | + // uncomment the following 3 lines to get ClientSecret from KeyVault |
| 142 | + string tenantId = Configuration.GetValue<string>("AzureAd:TenantId"); |
| 143 | + services.Configure<MicrosoftIdentityOptions>( |
| 144 | + options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "ENTER_YOUR_SECRET_NAME_HERE"); }); |
| 145 | +``` |
| 146 | + |
| 147 | +7. Your `ConfigureServices` method should now look like the following snippet: |
| 148 | + |
| 149 | +```CSharp |
| 150 | + public void ConfigureServices(IServiceCollection services) |
| 151 | + { |
| 152 | + string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); |
| 153 | + |
| 154 | + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) |
| 155 | + .AddMicrosoftIdentityWebApp(Configuration) |
| 156 | + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) |
| 157 | + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) |
| 158 | + .AddInMemoryTokenCaches(); |
| 159 | + |
| 160 | + // uncomment the following 3 lines to get ClientSecret from KeyVault |
| 161 | + string tenantId = Configuration.GetValue<string>("AzureAd:TenantId"); |
| 162 | + services.Configure<MicrosoftIdentityOptions>( |
| 163 | + options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "myClientSecret"); }); |
| 164 | + |
| 165 | + // ... more method code continues below |
| 166 | + } |
| 167 | +``` |
| 168 | + |
| 169 | +8. Add an environment variable to your App Service so your web app can find its key vault. |
| 170 | + |
| 171 | + 1. Go to the [Azure portal](https://portal.azure.com). Search for and select **App Service**, and then select your app. |
| 172 | + 1. Select **Configuration** blade on the left, then select **New Application Settings**. |
| 173 | + 1. Add a new variable, naming it **KEY_VAULT_URI**. Populate the value with the URI of your key vault, for example: `https://example-vault.vault.azure.net/` |
| 174 | + |
| 175 | +1. Re-deploy your project to Azure App Service. |
| 176 | + |
| 177 | + 1. Run the following command: |
| 178 | + |
| 179 | + ```console |
| 180 | + dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release |
| 181 | + ``` |
| 182 | + |
| 183 | + 1. Then, from the VS Code file explorer, right-click on the **bin/Release/netcoreapp3.1/publish** folder and select **Deploy to Web App**. If you are prompted to select an app, select one you created during this sample. |
| 184 | + |
| 185 | +1. The deployment status is available from the output window. Within a few minutes you'll be able to visit your now-secure app and sign in. |
| 186 | + |
| 187 | +## Optional - Handle Continuous Access Evaluation (CAE) challenge from Microsoft Graph |
| 188 | + |
| 189 | +Continuous access evaluation (CAE) enables web APIs to do just-in time token validation, for instance enforcing user session revocation in the case of password change/reset but there are other benefits. For details, see [Continuous access evaluation](https://docs.microsoft.com/azure/active-directory/conditional-access/concept-continuous-access-evaluation). |
| 190 | + |
| 191 | +Microsoft Graph is now CAE-enabled in Preview. This means that it can ask its clients for more claims when conditional access policies require it. Your can enable your application to be ready to consume CAE-enabled APIs by: |
| 192 | + |
| 193 | +1. Declaring that the client app is capable of handling claims challenges from the web API. |
| 194 | +2. Processing these challenges when thrown. |
| 195 | + |
| 196 | +### Declare the CAE capability in the configuration |
| 197 | + |
| 198 | +This sample declares that it's CAE-capable by adding a `ClientCapabilities` property in the configuration, whose value is `[ "cp1" ]`. |
| 199 | + |
| 200 | +```Json |
| 201 | +{ |
| 202 | + "AzureAd": { |
| 203 | + // ... |
| 204 | + // the following is required to handle Continuous Access Evaluation challenges |
| 205 | + "ClientCapabilities": [ "cp1" ], |
| 206 | + // ... |
| 207 | + }, |
| 208 | + // ... |
| 209 | +} |
| 210 | +``` |
0 commit comments