diff --git a/.gitignore b/.gitignore
index feb180eb..d021e5dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,3 +136,4 @@ packages
/4-WebApp-your-API/4-1-MyOrg/.vscode/settings.json
/5-WebApp-AuthZ/5-1-Roles/.vscode/settings.json
/.vscode
+/2-WebApp-graph-user/2-1-Call-MSGraph/Properties/PublishProfiles
diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/AboutTheCode.md b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/AboutTheCode.md
new file mode 100644
index 00000000..e69de29b
diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/CAE.md b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/CAE.md
new file mode 100644
index 00000000..2887d0ea
--- /dev/null
+++ b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/CAE.md
@@ -0,0 +1,57 @@
+### Process the CAE challenge from Microsoft Graph
+
+To process the CAE challenge from Microsoft Graph, the controller actions need to extract it from the `wwwAuthenticate` header. It is returned when MS Graph rejects a seemingly valid Access tokens for MS Graph. For this you need to:
+
+1. Inject and instance of `MicrosoftIdentityConsentAndConditionalAccessHandler` in the controller constructor. The beginning of the HomeController becomes:
+
+ ```CSharp
+ public class HomeController : Controller
+ {
+ private readonly ILogger _logger;
+ private readonly GraphServiceClient _graphServiceClient;
+ private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;
+ private string[] _graphScopes = new[] { "user.read" };
+ public HomeController(ILogger logger,
+ IConfiguration configuration,
+ GraphServiceClient graphServiceClient,
+ MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
+ {
+ _logger = logger;
+ _graphServiceClient = graphServiceClient;
+ this._consentHandler = consentHandler;
+ // Capture the Scopes for Graph that were used in the original request for an Access token (AT) for MS Graph as
+ // they'd be needed again when requesting a fresh AT for Graph during claims challenge processing
+ _graphScopes = configuration.GetValue("DownstreamApi:Scopes")?.Split(' ');
+ }
+
+ // more code here
+ ```
+1. The process to handle CAE challenges from MS Graph comprises of the following steps:
+ 1. Catch a Microsoft Graph SDK's `ServiceException` and extract the required `claims`. This is done by wrapping the call to Microsoft Graph into a try/catch block that processes the challenge:
+ ```CSharp
+ currentUser = await _graphServiceClient.Me.Request().GetAsync();
+ ```
+ 1. Then redirect the user back to Azure AD with the new requested `claims`. Azure AD will use this `claims` payload to discern what or if any additional processing is required, example being the user needs to sign-in again or do multi-factor authentication.
+ ```CSharp
+ try
+ {
+ currentUser = await _graphServiceClient.Me.Request().GetAsync();
+ }
+ // Catch CAE exception from Graph SDK
+ catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge"))
+ {
+ try
+ {
+ Console.WriteLine($"{svcex}");
+ string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders);
+ _consentHandler.ChallengeUser(_graphScopes, claimChallenge);
+ return new EmptyResult();
+ }
+ catch (Exception ex2)
+ {
+ _consentHandler.HandleException(ex2);
+ }
+ }
+ ```
+
+ The `AuthenticationHeaderHelper` class is available from the `Helpers\AuthenticationHeaderHelper.cs file`.
diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Deployment.md b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Deployment.md
new file mode 100644
index 00000000..45491952
--- /dev/null
+++ b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Deployment.md
@@ -0,0 +1,210 @@
+## Deployment
+
+### Deploying web app to Azure App Services
+
+There is one web app in this sample. To deploy it to **Azure App Services**, you'll need to:
+
+- create an **Azure App Service**
+- publish the projects to the **App Services**, and
+- update its client(s) to call the website instead of the local environment.
+
+#### Publish your files
+
+##### Publish using Visual Studio
+
+Follow the link to [Publish with Visual Studio](https://docs.microsoft.com/visualstudio/deployment/quickstart-deploy-to-azure).
+
+##### Publish using Visual Studio Code
+
+1. Open an instance of Visual Studio code set to the `WebApp-OpenIDConnect-DotNet-graph-v2` project folder.
+1. Install the VS Code extension [Azure App Service](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureappservice).
+1. Using the extension you just installed, sign in to **Azure App Service** using your Azure AD account.
+1. Choose `Terminal > New Terminal` from the VS Code menu to open a new terminal window in the project directory.
+1. Run the following command
+
+ ```console
+ dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release
+ ```
+
+1. A `publish` folder is created within the following folder: `bin/Release/netcoreapp3.1/`.
+1. From the VS Code file explorer, right-click on the **publish** folder and select **Deploy to Web App**.
+1. Select **Create New Web App**.
+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`.
+1. Select **Windows** as the OS. Press Enter.
+1. Select **.NET Core 3.1 (LTS)** as runtime stack.
+1. Select `Free` or any other option for your pricing tier.
+
+#### Update the Azure AD app registration (WebApp-OpenIDConnect-DotNet-graph-v2)
+
+1. Navigate back to to the [Azure portal](https://portal.azure.com).
+1. Go to the **Azure Active Directory** section, and then select **App registrations**.
+1. In the resulting screen, select the `WebApp-OpenIDConnect-DotNet-graph-v2` application.
+1. In the app's registration screen, select **Authentication** in the menu.
+ - 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:
+ - `https://example-domain.azurewebsites.net/`
+ - `https://example-domain.azurewebsites.net/signin-oidc`
+1. Update the **Front-channel logout URL** fields with the address of your service, for example `https://example-domain.azurewebsites.net`.
+
+> :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.
+
+### Enabling your code to get secrets from Key Vault using Managed Identity
+
+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.
+
+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).
+
+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)
+
+#### Set up your Managed Identity
+
+1. Navigate to [Azure portal](https://portal.azure.com) and select the **Azure App Service**.
+1. Find and select the App Service you've created previously.
+1. On App Service portal, select **Identity**.
+1. Within the **System assigned** tab, switch **Status** to **On**. Click **Save**.
+1. Record the **Object Id** that will appear, as you will need it in the next step.
+
+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)
+
+#### Set up your Key vault
+
+Before starting here, make sure:
+
+- You have an [Azure Subscription](https://azure.microsoft.com/free/).
+- 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.
+- Follow the guide to [create an Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/quick-create-portal).
+
+##### Upload your secret to KeyVault
+
+1. Navigate to your new key vault in the Azure portal.
+1. On the Key Vault settings pages, select **Secrets**.
+1. Click on **Generate/Import**.
+1. On the **Create a secret** screen choose the following values:
+ - **Upload options**: Manual.
+ - **Name**: Type a name for the secret. The secret name must be unique within a Key Vault. For example, `myClientSecret`
+ - **Value**: Copy and paste the value for the `ClientSecret` property (without quotes!) from your `appsettings.json` file.
+ - Leave the other values to their defaults. Click **Create**.
+
+##### Provide the managed identity access to Key Vault
+
+1. Navigate to your Key Vault in the portal.
+1. Select **Overview** > **Access policies**.
+1. Click on **Add Access Policy**.
+1. In the input box for **Secret permissions**, select **Get**.
+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.
+1. Click on **OK** to add the new Access Policy, then click **Save** to save the Access Policy.
+
+#### Modify your code to connect to Key Vault
+
+1. In the `appsettings.json` file, find and delete the `ClientSecret` property and its value.
+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/`
+1. Add the `Azure.Identity` NuGet package to the solution. This sample project has already added this package.
+1. Add the following directives to your `startup.cs`.
+
+ :information_source: this has been already added in the sample project.
+
+```CSharp
+using Azure;
+using Azure.Identity;
+using Azure.Security.KeyVault.Secrets;
+```
+
+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.
+
+ :information_source: this has already been added in the sample project.
+
+```CSharp
+ private string GetSecretFromKeyVault(string tenantId, string secretName)
+ {
+ // this should point to your vault's URI, like https://.vault.azure.net/
+ string uri = Environment.GetEnvironmentVariable("KEY_VAULT_URI");
+ DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions();
+
+ // Specify the tenant ID to use the dev credentials when running the app locally
+ options.VisualStudioTenantId = tenantId;
+ options.SharedTokenCacheTenantId = tenantId;
+ SecretClient client = new SecretClient(new Uri(uri), new DefaultAzureCredential(options));
+
+ // The secret name, for example if the full url to the secret is https://.vault.azure.net/secrets/Graph-App-Secret
+ Response secret = client.GetSecretAsync(secretName).Result;
+
+ return secret.Value.Value;
+ }
+```
+
+6. In your `Startup.cs` file, find the `ConfigureServices` method. Add the following code to call the GetSecretFromKeyVault method, right after `services.AddAuthentication`.
+
+ :information_source: In the sample project, this code is present but commented out by default. Uncomment it.
+
+ :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`.
+
+```CSharp
+ // uncomment the following 3 lines to get ClientSecret from KeyVault
+ string tenantId = Configuration.GetValue("AzureAd:TenantId");
+ services.Configure(
+ options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "ENTER_YOUR_SECRET_NAME_HERE"); });
+```
+
+7. Your `ConfigureServices` method should now look like the following snippet:
+
+```CSharp
+ public void ConfigureServices(IServiceCollection services)
+ {
+ string[] initialScopes = Configuration.GetValue("DownstreamApi:Scopes")?.Split(' ');
+
+ services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
+ .AddMicrosoftIdentityWebApp(Configuration)
+ .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
+ .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
+ .AddInMemoryTokenCaches();
+
+ // uncomment the following 3 lines to get ClientSecret from KeyVault
+ string tenantId = Configuration.GetValue("AzureAd:TenantId");
+ services.Configure(
+ options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "myClientSecret"); });
+
+ // ... more method code continues below
+ }
+```
+
+8. Add an environment variable to your App Service so your web app can find its key vault.
+
+ 1. Go to the [Azure portal](https://portal.azure.com). Search for and select **App Service**, and then select your app.
+ 1. Select **Configuration** blade on the left, then select **New Application Settings**.
+ 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/`
+
+1. Re-deploy your project to Azure App Service.
+
+ 1. Run the following command:
+
+ ```console
+ dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release
+ ```
+
+ 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.
+
+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.
+
+## Optional - Handle Continuous Access Evaluation (CAE) challenge from Microsoft Graph
+
+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).
+
+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:
+
+1. Declaring that the client app is capable of handling claims challenges from the web API.
+2. Processing these challenges when thrown.
+
+### Declare the CAE capability in the configuration
+
+This sample declares that it's CAE-capable by adding a `ClientCapabilities` property in the configuration, whose value is `[ "cp1" ]`.
+
+```Json
+{
+ "AzureAd": {
+ // ...
+ // the following is required to handle Continuous Access Evaluation challenges
+ "ClientCapabilities": [ "cp1" ],
+ // ...
+ },
+ // ...
+}
+```
diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/ExploreSample.md b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/ExploreSample.md
new file mode 100644
index 00000000..326aa818
--- /dev/null
+++ b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/ExploreSample.md
@@ -0,0 +1,86 @@
+## Explore the sample
+
+1. Open your web browser and make a request to the app at url `https://localhost:44321`. The app immediately attempts to authenticate you via the Microsoft identity platform. Sign in with a work or school account.
+2. Provide consent to the screen presented.
+3. Click on the **Profile** link on the top menu. The web app will make a call to the Microsoft Graph `/me` endpoint. You should see information about the signed-in user's account, as well as its picture, if these values are set in the account's profile.
+
+> Did the sample not work for you as expected? Did you encounter issues trying this sample? Then please reach out to us using the [GitHub Issues](../../../../issues) page.
+
+> [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbRz0h_jLR5HNJlvkZAewyoWxUNEFCQ0FSMFlPQTJURkJZMTRZWVJRNkdRMC4u)
+
+## About The code
+
+1. In this aspnetcore web project, first the packages `Microsoft.Identity.Web`, `Microsoft.Identity.Web.UI` and `Microsoft.Identity.Web.MicrosoftGraph` were added from NuGet. These libraries are used to simplify the process of signing-in a user and acquiring tokens for Microsoft Graph.
+
+2. Starting with the **Startup.cs** file :
+
+ - at the top of the file, the following two using directives were added:
+
+ ```CSharp
+ using Microsoft.Identity.Web;
+ using Microsoft.Identity.Web.UI;
+ ```
+
+ - in the `ConfigureServices` method, the following code was added, replacing any existing `AddAuthentication()` code:
+
+ ```CSharp
+
+ services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
+ .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
+ .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
+ .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
+ .AddInMemoryTokenCaches();
+
+ ```
+
+ `AddMicrosoftIdentityWebApp()` enables your application to sign-in a user with the Microsoft identity platform endpoint. This endpoint is capable of signing-in users both with their Work and School and Microsoft Personal accounts (if required).
+
+ `EnableTokenAcquisitionToCallDownstreamApi()` and `AddMicrosoftGraph` adds support to call Microsoft Graph. This lines ensures that the GraphAPIService benefits from the optimized `HttpClient` management by ASP.NET Core.
+
+3. In the `Controllers\HomeController.cs` file, the following code is added to allow calling MS Graph:
+
+ ```CSharp
+ private readonly ILogger _logger;
+ private readonly GraphServiceClient _graphServiceClient;
+
+ private readonly GraphServiceClient _graphServiceClient;
+ public HomeController(ILogger logger,
+ IConfiguration configuration,
+ GraphServiceClient graphServiceClient)
+ {
+ _logger = logger;
+ _graphServiceClient = graphServiceClient;
+ this._consentHandler = consentHandler;
+ }
+ ```
+
+4. In the `Profile()` action we make a call to the Microsoft Graph `/me` endpoint. In case a token cannot be acquired, a challenge is attempted to re-sign-in the user, and have them consent to the requested scopes. This is expressed declaratively by the `AuthorizeForScopes`attribute. This attribute is part of the `Microsoft.Identity.Web` project and automatically manages incremental consent.
+
+ ```CSharp
+ [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
+ public async Task Profile()
+ {
+ var me = await _graphServiceClient.Me.Request().GetAsync();
+ ViewData["Me"] = me;
+
+ try
+ {
+ // Get user photo
+ using (var photoStream = await _graphServiceClient.Me.Photo.Content.Request().GetAsync())
+ {
+ byte[] photoByte = ((MemoryStream)photoStream).ToArray();
+ ViewData["Photo"] = Convert.ToBase64String(photoByte);
+ }
+ }
+ catch (System.Exception)
+ {
+ ViewData["Photo"] = null;
+ }
+
+ return View();
+ }
+ ```
+
+5. Update `launchSetting.json`. Change the following values in the `Properties\launchSettings.json` file to ensure that you start your web app from `https://localhost:44321`:
+ - update the `sslPort` of the `iisSettings` section to be `44321`
+ - update the `applicationUrl` property to `https://localhost:44321`
diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/MoreInformation.md b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/MoreInformation.md
new file mode 100644
index 00000000..7337db64
--- /dev/null
+++ b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/MoreInformation.md
@@ -0,0 +1,15 @@
+## More information
+
+- [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/azure/active-directory/develop/)
+- [Overview of Microsoft Authentication Library (MSAL)](https://docs.microsoft.com/azure/active-directory/develop/msal-overview)
+- [Microsoft.Identity.Web](https://aka.ms/microsoft-identity-web)
+- [Quickstart: Register an application with the Microsoft identity platform (Preview)](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app)
+- [Understanding Azure AD application consent experiences](https://docs.microsoft.com/azure/active-directory/develop/application-consent-experience)
+- [Understand user and admin consent](https://docs.microsoft.com/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant#understand-user-and-admin-consent)
+- [Application and service principal objects in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals)
+- [National Clouds](https://docs.microsoft.com/azure/active-directory/develop/authentication-national-cloud#app-registration-endpoints)
+- [Azure AD code samples](https://docs.microsoft.com/azure/active-directory/develop/sample-v2-code)
+- [Managed Identities for Azure resources](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview)
+- [Azure Key Vault](https://azure.microsoft.com/services/key-vault/)
+- [Use Key Vault from App Service with Azure Managed Identity](https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/blob/master/README.md)
+- [Authentication Scenarios for Azure AD](https://docs.microsoft.com/azure/active-directory/develop/authentication-flows-app-scenarios).
diff --git a/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Overview.md b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Overview.md
new file mode 100644
index 00000000..091cb9d9
--- /dev/null
+++ b/2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Overview.md
@@ -0,0 +1,12 @@
+[](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=819)
+
+## Overview
+
+This sample demonstrates an ASP.NET Core web app that calls the Microsoft Graph API for a signed-in user.
+
+## Scenario
+
+1. The ASP.NET Core client web app uses the [Microsoft.Identity.Web](https://aka.ms/microsoft-identity-web) to sign a user in, and obtain a JWT [access Tokens](https://aka.ms/access-tokens) from **Azure AD**.
+1. The access token is used by the client app as a bearer token to call Microsoft Graph.
+
+
\ No newline at end of file
diff --git a/2-WebApp-graph-user/2-5-HybridFlow/.vscode/launch.json b/2-WebApp-graph-user/2-5-HybridFlow/.vscode/launch.json
new file mode 100644
index 00000000..97ef085e
--- /dev/null
+++ b/2-WebApp-graph-user/2-5-HybridFlow/.vscode/launch.json
@@ -0,0 +1,35 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "name": ".NET Core Launch (web)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/bin/Debug/net6.0/2-5-HybridFlow.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}",
+ "stopAtEntry": false,
+ // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
+ "serverReadyAction": {
+ "action": "openExternally",
+ "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
+ },
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "sourceFileMap": {
+ "/Views": "${workspaceFolder}/Views"
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/2-WebApp-graph-user/2-5-HybridFlow/.vscode/tasks.json b/2-WebApp-graph-user/2-5-HybridFlow/.vscode/tasks.json
new file mode 100644
index 00000000..124b5e60
--- /dev/null
+++ b/2-WebApp-graph-user/2-5-HybridFlow/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/2-5-HybridFlow.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/2-5-HybridFlow.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/2-5-HybridFlow.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/2-WebApp-graph-user/2-5-HybridFlow/AppCreationScripts/sample1.json b/2-WebApp-graph-user/2-5-HybridFlow/AppCreationScripts/sample1.json
new file mode 100644
index 00000000..bb5dbdc9
--- /dev/null
+++ b/2-WebApp-graph-user/2-5-HybridFlow/AppCreationScripts/sample1.json
@@ -0,0 +1,125 @@
+{
+ "Sample": {
+ "Client": "ASP.NET Core Web App",
+ "Description": "Sign-in users interactively server-side (Confidential client) and silently acquire token for MS Graph for a Single-page app (SPA)",
+ "Endpoint": "AAD v2.0",
+ "Languages": [
+ "csharp",
+ "javascript"
+ ],
+ "Level": 200,
+ "Products": [
+ "aspnet-core",
+ "azure-active-directory"
+ ],
+ "RepositoryUrl": "active-directory-aspnetcore-webapp-openidconnect-v2",
+ "Service": "Microsoft Graph",
+ "Title": "active-directory-aspnetcore-webapp-openidconnect-v2"
+ },
+ "ReadmeScenario": {
+ "IncludeFilePath": "../ReadmeFiles/ReadmeScenario.md",
+ "Image": "../ReadmeFiles/topology.png",
+ "AdditionalNotes": ""
+ },
+ "ReadmePrerequirements": {
+ "FreeText": "",
+ "IncludeFilePath": ""
+ },
+ "ReadmeSetup": {
+ "FreeText": "",
+ "IncludeFilePath": "../ReadmeFiles/ReadmeSetup.md",
+ "UseNewSetup": "1",
+ "CertificateOption": "1",
+ "CreateProjectIncludeFilePath": "",
+ "AppRegistrationIncludeFilePath": "",
+ "RunSampleIncludeFilePath": ""
+ },
+ "ReadmeTroubleshooting": {
+ "IncludeFilePath": "../ReadmeFiles/ReadmeTroubleshooting.md"
+ },
+ "ReadmeNextSteps": {
+ "FreeText": "",
+ "IncludeFilePath": "../ReadmeFiles/ReadmeNextSteps.md"
+ },
+ "ReadmeContributing": {
+ "FreeText": "",
+ "IncludeFilePath": "../ReadmeFiles/ReadmeContributing.md"
+ },
+ "ReadmeHowTheCodeWasCreated": {
+ "IncludeFilePath": ""
+ },
+ "ReadmeAboutTheCode": {
+ "IncludeFilePath": "../ReadmeFiles/ReadmeAboutTheCode.md"
+ },
+ "ReadmeAzureDeploy": {
+ "FreeText": "",
+ "IncludeFilePath": ""
+ },
+ "ReadmeUsingTheSample": {
+ "IncludeFilePath": "../ReadmeFiles/ReadmeUsingTheSample.md"
+ },
+ "ReadmeLearnMore": {
+ "IncludeFilePath": "../ReadmeFiles/ReadmeLearnMore.md"
+ },
+ "AADApps": [
+ {
+ "AllowImplicitFlow": true,
+ "EnableIdTokenIssuance": true,
+ "Audience": "AzureADMyOrg",
+ "PasswordCredentials": "Auto",
+ "HybridRedirectUris": [
+ {
+ "ApplicationKind": "WebApp",
+ "Uri": "https://localhost:44321/signin-oidc"
+ },
+ {
+ "ApplicationKind": "SinglePageApplication",
+ "Uri": "https://localhost:44321/"
+ }
+ ],
+ "Id": "HybridFlowAspNetCore",
+ "Kind": "WebApp",
+ "ManualSteps": [],
+ "Name": "HybridFlowAspNetCore",
+ "RequiredResourcesAccess": [
+ {
+ "DelegatedPermissions": [
+ "User.Read",
+ "Contacts.Read"
+ ],
+ "Resource": "Microsoft Graph"
+ }
+ ],
+ "Sample": {
+ "SampleSubPath": "2-WebApp-graph-user\\2-5-HybridFlow",
+ "ProjectDirectory": "."
+ },
+ "CSharpConfigurations": [
+ {
+ "AppSettingsJson": {
+ "AzureAd": {
+ "Instance": "https://login.microsoftonline.com/",
+ "CallbackPath": "/signin-oidc",
+ "ClientCertificates": [],
+ "WithSpaAuthCode": true,
+ "ClientSecret": "Auto"
+ },
+ "DownStreamApi": {
+ "BaseUrl": "https://graph.microsoft.com/v1.0",
+ "Scopes": "User.Read Contacts.Read"
+ },
+ "RelativePath": "..\\"
+ }
+ }
+ ]
+ }
+ ],
+ "CodeConfiguration": [
+ {
+ "App": "HybridFlowAspNetCore",
+ "Mappings": [],
+ "SettingFile": "\\..\\appsettings.json",
+ "SettingKind": "JSON"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/4-WebApp-your-API/4-1-MyOrg/4-1-MyOrg.sln b/4-WebApp-your-API/4-1-MyOrg/4-1-MyOrg.sln
index a1316840..95b13f75 100644
--- a/4-WebApp-your-API/4-1-MyOrg/4-1-MyOrg.sln
+++ b/4-WebApp-your-API/4-1-MyOrg/4-1-MyOrg.sln
@@ -6,8 +6,8 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DDAC3758-9CAE-4790-84B1-5B6258BAE44E}"
ProjectSection(SolutionItems) = preProject
README-incremental-instructions.md = README-incremental-instructions.md
- README-use-keyvault-certificate.md = README-use-keyvault-certificate.md
README.md = README.md
+ README-use-certificate.md = README-use-certificate.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoListService", "TodoListService\TodoListService.csproj", "{A55C5542-9C7B-4A34-932C-05DAF36CED0C}"
@@ -31,7 +31,12 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadmeFiles", "ReadmeFiles", "{2A7989B7-23E0-46D4-8F2A-3D069084F65D}"
ProjectSection(SolutionItems) = preProject
ReadmeFiles\ReadmeAboutTheCode.md = ReadmeFiles\ReadmeAboutTheCode.md
+ ReadmeFiles\ReadmeAzureDeploy.md = ReadmeFiles\ReadmeAzureDeploy.md
+ ReadmeFiles\ReadmeExploreTheSample.md = ReadmeFiles\ReadmeExploreTheSample.md
+ ReadmeFiles\ReadmeHowTheCodeWasCreated.md = ReadmeFiles\ReadmeHowTheCodeWasCreated.md
+ ReadmeFiles\ReadmeLearnMore.md = ReadmeFiles\ReadmeLearnMore.md
ReadmeFiles\ReadmeScenario.md = ReadmeFiles\ReadmeScenario.md
+ ReadmeFiles\ReadmeTroubleshooting.md = ReadmeFiles\ReadmeTroubleshooting.md
ReadmeFiles\topology.png = ReadmeFiles\topology.png
EndProjectSection
EndProject
diff --git a/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/AppCreationScripts.md b/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/AppCreationScripts.md
index 7512ab4c..6e062df6 100644
--- a/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/AppCreationScripts.md
+++ b/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/AppCreationScripts.md
@@ -1,45 +1,35 @@
-# Registering the sample apps with the Microsoft identity platform and updating the configuration files using PowerShell
+# Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell
## Overview
### Quick summary
-1. On Windows run PowerShell as **Administrator** 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
+ .\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:
-
-- [Registering the sample apps with the Microsoft identity platform and updating the configuration files using PowerShell](#Registering-the-sample-apps-with-the-Microsoft-identity-platform-and-updating-the-configuration-files-using-PowerShell)
- - [Overview](#Overview)
- - [Quick summary](#Quick-summary)
- - [More details](#More-details)
- - [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 (non-interactive)](#Option-2-non-interactive)
- - [Option 3 (Interactive, but create apps in a specified tenant)](#Option-3-Interactive-but-create-apps-in-a-specified-tenant)
- - [Option 4 (non-interactive, and create apps in a specified tenant)](#Option-4-non-interactive-and-create-apps-in-a-specified-tenant)
- - [Running the script on Azure Sovereign clouds](#Running-the-script-on-Azure-Sovereign-clouds)
+- [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)
## Goal of the provided scripts
@@ -50,14 +40,14 @@ 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
@@ -75,23 +65,23 @@ The `Configure.ps1` will stop if it tries to create an Azure AD application whic
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
```
-### (Optionally) install AzureAD PowerShell modules
+### (Optionally) install Microsoft.Graph.Applications 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:
+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. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
+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).
+ 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
@@ -106,44 +96,29 @@ The scripts install the required PowerShell module (AzureAD) for the current use
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. this just works!
+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 identity 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
@@ -155,32 +130,19 @@ $tenantId = "yourTenantIdGuid"
. .\Configure.ps1 -TenantId $tenantId
```
-#### Option 4 (non-interactive, and create apps in a specified tenant)
-
-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:
-
-```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
-```
-
### Running the script on Azure Sovereign clouds
-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`.
+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`.
The acceptable values for this parameter are:
- AzureCloud
- AzureChinaCloud
- AzureUSGovernment
-- AzureGermanyCloud
Example:
```PowerShell
- . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud"
- . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud"
+ . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment"
+ . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment"
```
diff --git a/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Cleanup.ps1 b/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Cleanup.ps1
index ee69aa0d..9fb78000 100644
--- a/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Cleanup.ps1
+++ b/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Cleanup.ps1
@@ -1,3 +1,4 @@
+
[CmdletBinding()]
param(
[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')]
@@ -41,7 +42,7 @@ Function Cleanup
}
catch
{
- Write-Host "Unable to remove the application 'TodoListService-aspnetcore-webapi' . Try deleting manually." -ForegroundColor White -BackgroundColor Red
+ Write-Host "Unable to remove the application 'TodoListService-aspnetcore-webapi' . Try deleting manually." -ForegroundColor White -BackgroundColor Red
}
Write-Host "Making sure there are no more (TodoListService-aspnetcore-webapi) applications found, will remove if needed..."
@@ -65,7 +66,7 @@ Function Cleanup
}
catch
{
- Write-Host "Unable to remove ServicePrincipal 'TodoListService-aspnetcore-webapi' . Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red
+ Write-Host "Unable to remove ServicePrincipal 'TodoListService-aspnetcore-webapi' . Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red
}
Write-Host "Removing 'client' (TodoListClient-aspnetcore-webapi) if needed"
try
@@ -74,7 +75,7 @@ Function Cleanup
}
catch
{
- Write-Host "Unable to remove the application 'TodoListClient-aspnetcore-webapi' . Try deleting manually." -ForegroundColor White -BackgroundColor Red
+ Write-Host "Unable to remove the application 'TodoListClient-aspnetcore-webapi' . Try deleting manually." -ForegroundColor White -BackgroundColor Red
}
Write-Host "Making sure there are no more (TodoListClient-aspnetcore-webapi) applications found, will remove if needed..."
@@ -98,10 +99,11 @@ Function Cleanup
}
catch
{
- Write-Host "Unable to remove ServicePrincipal 'TodoListClient-aspnetcore-webapi' . Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red
+ Write-Host "Unable to remove ServicePrincipal 'TodoListClient-aspnetcore-webapi' . Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red
}
# remove self-signed certificate
- Get-ChildItem -Path Cert:\CurrentUser\My | where { $_.subject -eq "the certificate will be named by application name" } | Remove-Item
+ Write-Host "Removing CN=TodoListClient-aspnetcore-webapi certificate from Cert:/CurrentUser/My"
+ Get-ChildItem -Path Cert:\CurrentUser\My | where { $_.subject -eq "CN=TodoListClient-aspnetcore-webapi" } | Remove-Item
}
if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) {
@@ -115,4 +117,3 @@ Cleanup -tenantId $tenantId -environment $azureEnvironmentName
Write-Host "Disconnecting from tenant"
Disconnect-MgGraph
-
diff --git a/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Configure.ps1 b/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Configure.ps1
index 3767f514..2457e59a 100644
--- a/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Configure.ps1
+++ b/4-WebApp-your-API/4-1-MyOrg/AppCreationScripts-withCert/Configure.ps1
@@ -1,4 +1,4 @@
-
+
[CmdletBinding()]
param(
[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')]
@@ -16,20 +16,6 @@ param(
There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
#>
-# 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] $durationInMonths)
-{
- $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
-}
-
# Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
# The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
# described in $permissionType
@@ -154,9 +140,24 @@ Function CreateAppRole([string] $types, [string] $name, [string] $description)
$appRole.Value = $name;
return $appRole
}
+Function CreateOptionalClaim([string] $name)
+{
+ <#.Description
+ This function creates a new Azure AD optional claims with default and provided values
+ #>
+
+ $appClaim = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim
+ $appClaim.AdditionalProperties = New-Object System.Collections.Generic.List[string]
+ $appClaim.Source = $null
+ $appClaim.Essential = $false
+ $appClaim.Name = $name
+ return $appClaim
+}
Function ConfigureApplications
{
+ $isOpenSSl = 'N' #temporary disable open certificate creation
+
<#.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)
@@ -188,6 +189,10 @@ Function ConfigureApplications
@{ `
HomePageUrl = "https://localhost:44351"; `
} `
+ -Api `
+ @{ `
+ RequestedAccessTokenVersion = 2 `
+ } `
-SignInAudience AzureADMyOrg `
#end of command
$serviceIdentifierUri = 'api://'+$serviceAadApplication.AppId
@@ -204,6 +209,28 @@ Function ConfigureApplications
New-MgApplicationOwnerByRef -ApplicationId $serviceAadApplication.Id -BodyParameter = @{"@odata.id" = "htps://graph.microsoft.com/v1.0/directoryObjects/$user.ObjectId"}
Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($serviceServicePrincipal.DisplayName)'"
}
+
+ # Add Claims
+
+ $optionalClaims = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaims
+ $optionalClaims.AccessToken = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim]
+ $optionalClaims.IdToken = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim]
+ $optionalClaims.Saml2Token = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim]
+
+
+ # Add Optional Claims
+
+ $newClaim = CreateOptionalClaim -name "idtyp"
+ $optionalClaims.AccessToken += ($newClaim)
+ Update-MgApplication -ApplicationId $serviceAadApplication.Id -OptionalClaims $optionalClaims
+
+ # Add application Roles
+ $appRoles = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole]
+ $newRole = CreateAppRole -types "Application" -name "ToDoList.Read.All" -description "Allow application to read all ToDo list items"
+ $appRoles.Add($newRole)
+ $newRole = CreateAppRole -types "Application" -name "ToDoList.ReadWrite.All" -description "Allow application to read and write into ToDo list"
+ $appRoles.Add($newRole)
+ Update-MgApplication -ApplicationId $serviceAadApplication.Id -AppRoles $appRoles
# rename the user_impersonation scope if it exists to match the readme steps or add a new scope
@@ -228,14 +255,14 @@ Function ConfigureApplications
-userConsentDisplayName "Access TodoListService-aspnetcore-webapi" `
-userConsentDescription "Allow the application to access TodoListService-aspnetcore-webapi on your behalf." `
-adminConsentDisplayName "Access TodoListService-aspnetcore-webapi" `
- -adminConsentDescription "Allows the app to have the same access to information in the directory on behalf of the signed-in user."
+ -adminConsentDescription "Allows the app to have the same access to information in the directory on behalf of an admin."
$scopes.Add($scope)
- $scope = CreateScope -value ToDoList.Write `
+ $scope = CreateScope -value ToDoList.ReadWrite `
-userConsentDisplayName "Access TodoListService-aspnetcore-webapi" `
-userConsentDescription "Allow the application to access TodoListService-aspnetcore-webapi on your behalf." `
-adminConsentDisplayName "Access TodoListService-aspnetcore-webapi" `
- -adminConsentDescription "Allows the app to have the same access to information in the directory on behalf of the signed-in user."
+ -adminConsentDescription "Allows the app to have the same access to information in the directory on behalf of an admin."
$scopes.Add($scope)
@@ -250,60 +277,51 @@ Function ConfigureApplications
# Create the client AAD application
Write-Host "Creating the AAD application (TodoListClient-aspnetcore-webapi)"
- # Get a 6 months application key for the client Application
- $fromDate = [DateTime]::Now;
- $key = CreateAppKey -fromDate $fromDate -durationInMonths 6
-
# create the application
$clientAadApplication = New-MgApplication -DisplayName "TodoListClient-aspnetcore-webapi" `
-Web `
@{ `
- RedirectUris = "https://localhost:44321/", "https://localhost:44321/signin-oidc"; `
+ RedirectUris = "https://localhost:44321/signin-oidc"; `
HomePageUrl = "https://localhost:44321/"; `
LogoutUrl = "https://localhost:44321/signout-oidc"; `
} `
-SignInAudience AzureADMyOrg `
#end of command
- #add password to the application
- $pwdCredential = Add-MgApplicationPassword -ApplicationId $clientAadApplication.Id -PasswordCredential $key
- $clientAppKey = $pwdCredential.SecretText
$tenantName = (Get-MgApplication -ApplicationId $clientAadApplication.Id).PublisherDomain
Update-MgApplication -ApplicationId $clientAadApplication.Id -IdentifierUris @("https://$tenantName/TodoListClient-aspnetcore-webapi")
-
- # Generate a certificate
- Write-Host "Creating the client application (TodoListClient-aspnetcore-webapi)"
+ # Generate a certificate
+ Write-Host "Creating the client application (TodoListClient-aspnetcore-webapi)"
- $certificateName = 'TodoListClient-aspnetcore-webapi'
+ $certificateName = 'TodoListClient-aspnetcore-webapi'
- # temporarily disable the option and procees to certificate creation
- #$isOpenSSL = Read-Host ' By default certificate is generated using New-SelfSignedCertificate. Do you want to generate cert using OpenSSL(Y/N)?'
- $isOpenSSl = 'N'
- if($isOpenSSL -eq 'Y')
- {
- $certificate=openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -keyout "$certificateName.key" -out "$certificateName.cer" -nodes -batch
- openssl pkcs12 -export -out "$certificateName.pfx" -inkey $certificateName.key -in "$certificateName.cer"
- }
- else
- {
- $certificate=New-SelfSignedCertificate -Subject $certificateName `
- -CertStoreLocation "Cert:\CurrentUser\My" `
- -KeyExportPolicy Exportable `
- -KeySpec Signature
-
- $thumbprint = $certificate.Thumbprint
- $certificatePassword = Read-Host -Prompt "Enter password for your certificate (Please remember the password, you will need it when uploading to KeyVault): " -AsSecureString
- Write-Host "Exporting certificate as a PFX file"
- Export-PfxCertificate -Cert "Cert:\Currentuser\My\$thumbprint" -FilePath "$pwd\$certificateName.pfx" -ChainOption EndEntityCertOnly -NoProperties -Password $certificatePassword
- Write-Host "PFX written to:"
- Write-Host "$pwd\$certificateName.pfx"
-
- # Add a Azure Key Credentials from the certificate for the application
- $clientKeyCredentials = Update-MgApplication -ApplicationId $clientAadApplication.Id `
- -KeyCredentials @(@{Type = "AsymmetricX509Cert"; Usage = "Verify"; Key= $certificate.RawData; StartDateTime = $certificate.NotBefore; EndDateTime = $certificate.NotAfter;})
+ # temporarily disable the option and procees to certificate creation
+ #$isOpenSSL = Read-Host ' By default certificate is generated using New-SelfSignedCertificate. Do you want to generate cert using OpenSSL(Y/N)?'
+ $isOpenSSl = 'N'
+ if($isOpenSSL -eq 'Y')
+ {
+ $certificate=openssl req -x509 -newkey rsa:2048 -days 365 -keyout "$certificateName.key" -out "$certificateName.cer" -subj "/CN=$certificateName.com" -nodes
+ openssl pkcs12 -export -out "$certificateName.pfx" -inkey $certificateName.key -in "$certificateName.cer"
+ }
+ else
+ {
+ $certificate=New-SelfSignedCertificate -Subject $certificateName `
+ -CertStoreLocation "Cert:\CurrentUser\My" `
+ -KeyExportPolicy Exportable `
+ -KeySpec Signature
+
+ $thumbprint = $certificate.Thumbprint
+ $certificatePassword = Read-Host -Prompt "Enter password for your certificate (Please remember the password, you will need it when uploading to KeyVault): " -AsSecureString
+ Write-Host "Exporting certificate as a PFX file"
+ Export-PfxCertificate -Cert "Cert:\Currentuser\My\$thumbprint" -FilePath "$pwd\$certificateName.pfx" -ChainOption EndEntityCertOnly -NoProperties -Password $certificatePassword
+ Write-Host "PFX written to:"
+ Write-Host "$pwd\$certificateName.pfx"
+
+ # Add a Azure Key Credentials from the certificate for the application
+ $clientKeyCredentials = Update-MgApplication -ApplicationId $clientAadApplication.Id `
+ -KeyCredentials @(@{Type = "AsymmetricX509Cert"; Usage = "Verify"; Key= $certificate.RawData; StartDateTime = $certificate.NotBefore; EndDateTime = $certificate.NotAfter;})
- }
-
+ }
# create the service principal of the newly created application
$currentAppId = $clientAadApplication.AppId
@@ -316,6 +334,20 @@ Function ConfigureApplications
New-MgApplicationOwnerByRef -ApplicationId $clientAadApplication.Id -BodyParameter = @{"@odata.id" = "htps://graph.microsoft.com/v1.0/directoryObjects/$user.ObjectId"}
Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
}
+
+ # Add Claims
+
+ $optionalClaims = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaims
+ $optionalClaims.AccessToken = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim]
+ $optionalClaims.IdToken = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim]
+ $optionalClaims.Saml2Token = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim]
+
+
+ # Add Optional Claims
+
+ $newClaim = CreateOptionalClaim -name "acct"
+ $optionalClaims.IdToken += ($newClaim)
+ Update-MgApplication -ApplicationId $clientAadApplication.Id -OptionalClaims $optionalClaims
Write-Host "Done creating the client application (TodoListClient-aspnetcore-webapi)"
# URL of the AAD application in the Azure portal
@@ -323,12 +355,11 @@ Function ConfigureApplications
$clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.Id+"/isMSAApp/"
Add-Content -Value "client | $currentAppId | TodoListClient-aspnetcore-webapi |
" -Path createdApps.html
$requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess]
-
# Add Required Resources Access (from 'client' to 'service')
Write-Host "Getting access from 'client' to 'service'"
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "TodoListService-aspnetcore-webapi" `
- -requiredDelegatedPermissions "ToDoList.Read|ToDoList.Write" `
+ -requiredDelegatedPermissions "ToDoList.Read|ToDoList.ReadWrite" `
$requiredResourcesAccess.Add($requiredPermissions)
@@ -345,12 +376,18 @@ Function ConfigureApplications
# Update config file for 'client'
$configFile = $pwd.Path + "\..\Client\appsettings.json"
- $dictionary = @{ "Domain" = $tenantName;"TenantId" = $tenantId;"ClientId" = $clientAadApplication.AppId;"KeyVaultCertificateName" = $certificateName;"TodoListScopes" = "api://$($serviceAadApplication.AppId)/ToDoList.Read api://$($serviceAadApplication.AppId)/ToDoList.Write";"TodoListBaseAddress" = $serviceAadApplication.Web.HomePageUrl };
+ $dictionary = @{ "Domain" = $tenantName;"TenantId" = $tenantId;"ClientId" = $clientAadApplication.AppId;"KeyVaultCertificateName" = $certificateName;"TodoListScopes" = "api://$($serviceAadApplication.AppId)/ToDoList.Read api://$($serviceAadApplication.AppId)/ToDoList.ReadWrite";"TodoListBaseAddress" = $serviceAadApplication.Web.HomePageUrl };
Write-Host "Updating the sample code ($configFile)"
UpdateTextFile -configFilePath $configFile -dictionary $dictionary
- if($isOpenSSL -eq 'Y')
+ Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
+ Write-Host "IMPORTANT: Please follow the instructions below to complete a few manual step(s) in the Azure portal":
+ Write-Host "- For service"
+ Write-Host " - Navigate to $servicePortalUrl"
+ Write-Host " - Application 'service' publishes application permissions. Do remember to navigate to any client app(s) registration in the app portal and consent for those, if required" -ForegroundColor Red
+ Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
+ if($isOpenSSL -eq 'Y')
{
Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
Write-Host "You have generated certificate using OpenSSL so follow below steps: "
@@ -358,7 +395,7 @@ Function ConfigureApplications
Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
}
Add-Content -Value "