page_type | languages | products | name | urlFragment | description | ||||
---|---|---|---|---|---|---|---|---|---|
sample |
|
|
Enable your ASP.NET Core web app to sign in users and call Microsoft Graph with the Microsoft identity platform |
active-directory-aspnetcore-webapp-openidconnect-v2 |
This sample demonstrates a ASP.NET Core Web App calling the Microsoft Graph |
Enable your ASP.NET Core web app to sign in users and call Microsoft Graph with the Microsoft identity platform
- Overview
- Scenario
- Prerequisites
- Setup
- Run the sample
- Explore the sample
- About The code
- Deployment
- Optional - Handle Continuous Access Evaluation (CAE) challenge from Microsoft Graph
- More information
- Community Help and Support
- Contributing
This sample demonstrates an ASP.NET Core web app that calls the Microsoft Graph API for a signed-in user.
- The ASP.NET Core client web app uses the Microsoft.Identity.Web to sign a user in, and obtain a JWT access Tokens from Azure AD.
- The access token is used by the client app as a bearer token to call Microsoft Graph.
- Either Visual Studio or Visual Studio Code and .NET Core SDK
- An Azure AD tenant. For more information see: How to get an Azure AD tenant
- A user account in your Azure AD tenant. This sample will not work with a personal Microsoft account. If you're signed in to the Azure portal with a personal account and have not created a user account in your directory before, you will need need to create one before proceeding.
From your shell or command line:
git clone https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2.git
or download and extract the repository .zip file.
⚠️ To avoid path length limitations on Windows, we recommend cloning into a directory near the root of your drive.
Go to the "2-WebApp-graph-user\2-1-Call-MSGraph"
folder
cd "2-WebApp-graph-user\2-1-Call-MSGraph"
Developers who wish to increase their familiarity with programming for Microsoft Graph are advised to go through the An introduction to Microsoft Graph for developers recorded session.
dotnet restore WebApp-OpenIDConnect-DotNet-graph.csproj
There is one project in this sample. To register it, you can:
- follow the steps below for manually register your apps
- or use PowerShell scripts that:
- automatically creates the Azure AD applications and related objects (passwords, permissions, dependencies) for you.
- modify the projects' configuration files.
Expand this section if you want to use this automation:
⚠️ If you have never used Azure AD Powershell before, we recommend you go through the App Creation Scripts once to ensure that your environment is prepared correctly for this step.
- On Windows, run PowerShell as Administrator and navigate to the folder that contains this readme file.
- If you have never used Azure AD Powershell before, we recommend you go through the App Creation Scripts once to ensure that your environment is prepared correctly for this step.
- In PowerShell run:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
- Run the script to create your Azure AD application and configure the code of the sample application accordingly.
- In PowerShell run:
cd .\AppCreationScripts\
.\Configure.ps1
Other ways of running the scripts are described in App Creation Scripts The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios.
As a first step you'll need to:
- Sign in to the Azure portal.
- 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.
- Navigate to the Azure portal and select the Azure AD service.
- Select the App Registrations blade on the left, then select New registration.
- 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-OpenIDConnect-DotNet-graph-v2
. - Under Supported account types, select Accounts in this organizational directory only.
- In the Redirect URI (optional) section, select Web in the combo-box and enter the following redirect URI:
https://localhost:44321/
.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.
- In the Name section, enter a meaningful application name that will be displayed to users of the app, for example
- Select Register to create the application.
- In the app's registration screen, find and note the Application (client) ID. You'll need to use this value in your app's configuration files.
- In the app's registration screen, select Authentication in the menu.
- If you don't have a platform added, select Add a platform and select the Web option.
- In the Redirect URIs section, enter the following redirect URIs.
https://localhost:44321/signin-oidc
- In the Front-channel logout URL section, set it to
https://localhost:44321/signout-oidc
.
- Select Save to save your changes.
- In the app's registration screen, select the Certificates & secrets blade in the left to open the page where we can generate secrets and upload certificates.
- In the Client secrets section, select New client secret:
- Type a key description (for instance
app secret
), - Select one of the available key durations (6 months, 12 months or Custom) as per your security posture.
- The generated key value will be displayed when you select the Add button. Copy and save the generated value for use in later steps.
- 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.
- Type a key description (for instance
- 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.
- Select the Add a permission button and then,
- Ensure that the Microsoft APIs tab is selected.
- In the Commonly used Microsoft APIs section, select Microsoft Graph
- In the Delegated permissions section, select the User.Read in the list. Use the search box if necessary.
- Select the Add permissions button at the bottom.
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".
- Open the
appsettings.json
file. - Find the key
ClientId
and replace the existing value with the application ID (clientId) ofWebApp-OpenIDConnect-DotNet-graph-v2
app copied from the Azure portal. - Find the key
TenantId
and replace the existing value with your Azure AD tenant ID. - Find the key
Domain
and replace the existing value with your Azure AD tenant name. - Find the key
ClientSecret
and replace the existing value with the key you saved during the creation ofWebApp-OpenIDConnect-DotNet-graph-v2
copied from the Azure portal.
-
In case you want to deploy your app in Sovereign or national clouds, ensure the
GraphApiUrl
andInstance
option matches the your requirements. The default values are set to Microsoft Graph in the Azure public cloud. You may skip this point if it does not apply to you."Instance": "https://login.microsoftonline.com/", "GraphApiUrl": "https://graph.microsoft.com/v1.0"
For Visual Studio Users
Clean the solution, rebuild the solution, and run it. You might want to go into the solution properties and set the right startup project first.
dotnet run
- 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. - Provide consent to the screen presented.
- 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 page.
-
In this aspnetcore web project, first the packages
Microsoft.Identity.Web
,Microsoft.Identity.Web.UI
andMicrosoft.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. -
Starting with the Startup.cs file :
-
at the top of the file, the following two using directives were added:
using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI;
-
in the
ConfigureServices
method, the following code was added, replacing any existingAddAuthentication()
code:
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()
andAddMicrosoftGraph
adds support to call Microsoft Graph. This lines ensures that the GraphAPIService benefits from the optimizedHttpClient
management by ASP.NET Core. -
-
In the
Controllers\HomeController.cs
file, the following code is added to allow calling MS Graph:
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphServiceClient;
public HomeController(ILogger<HomeController> logger,
IConfiguration configuration,
GraphServiceClient graphServiceClient)
{
_logger = logger;
_graphServiceClient = graphServiceClient;
this._consentHandler = consentHandler;
}
-
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 theAuthorizeForScopes
attribute. This attribute is part of theMicrosoft.Identity.Web
project and automatically manages incremental consent.[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")] public async Task<IActionResult> 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(); }
-
Update
launchSetting.json
. Change the following values in theProperties\launchSettings.json
file to ensure that you start your web app fromhttps://localhost:44321
:- update the
sslPort
of theiisSettings
section to be44321
- update the
applicationUrl
property tohttps://localhost:44321
- update the
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.
Follow the link to Publish with Visual Studio.
-
Open an instance of Visual Studio code set to the
WebApp-OpenIDConnect-DotNet-graph-v2
project folder. -
Install the VS Code extension Azure App Service.
-
Using the extension you just installed, sign in to Azure App Service using your Azure AD account.
-
Choose
Terminal > New Terminal
from the VS Code menu to open a new terminal window in the project directory. -
Run the following command
dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release
-
A
publish
folder is created within the following folder:bin/Release/netcoreapp3.1/
. -
From the VS Code file explorer, right-click on the publish folder and select Deploy to Web App.
-
Select Create New Web App.
-
Enter a unique name for the app, for example,
WebApp-OpenIDConnect-DotNet-graph-v2
. If you choseexample-domain
for your app name, your app's domain name will behttps://example-domain.azurewebsites.net
. -
Select Windows as the OS. Press Enter.
-
Select .NET Core 3.1 (LTS) as runtime stack.
-
Select
Free
or any other option for your pricing tier.
- Navigate back to to the Azure portal.
- Go to the Azure Active Directory section, and then select App registrations.
- In the resulting screen, select the
WebApp-OpenIDConnect-DotNet-graph-v2
application. - 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
- 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
- Update the Front-channel logout URL fields with the address of your service, for example
https://example-domain.azurewebsites.net
.
⚠️ 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.
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 and access it via managed Identities for Azure resources.
We will follow the steps broadly outlined in the guide: Use Key Vault from App Service with Azure Managed Identity
- Navigate to Azure portal and select the Azure App Service.
- Find and select the App Service you've created previously.
- On App Service portal, select Identity.
- Within the System assigned tab, switch Status to On. Click Save.
- 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
Before starting here, make sure:
- You have an Azure Subscription.
- You have a working and deployed application as an Azure App Service following the steps listed at Deploying web app to Azure App Services above.
- Follow the guide to create an Azure Key Vault.
- Navigate to your new key vault in the Azure portal.
- On the Key Vault settings pages, select Secrets.
- Click on Generate/Import.
- 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 yourappsettings.json
file. - Leave the other values to their defaults. Click Create.
- Navigate to your Key Vault in the portal.
- Select Overview > Access policies.
- Click on Add Access Policy.
- In the input box for Secret permissions, select Get.
- Click on Select Principal, add the system-assigned managed identity that you have created in the steps before. You can use the Object Id you have recorded previously to search for it.
- Click on OK to add the new Access Policy, then click Save to save the Access Policy.
- In the
appsettings.json
file, find and delete theClientSecret
property and its value. - In the
Properties\launchSettings.json
file, find the stringENTER_YOUR_KEY_VAULT_URI
and replace it with the URI of your Key Vault, for example:https://example-vault.vault.azure.net/
- Add the
Azure.Identity
NuGet package to the solution. This sample project has already added this package. - Add the following directives to your
startup.cs
. ℹ️ this has been already added in the sample project.
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
- In your
Startup.cs
file, you must create aGetSecretFromKeyVault
method. This method sets up the Azure Key Vault client and returns the secret that is required. ℹ️ this has already been added in the sample project.
private string GetSecretFromKeyVault(string tenantId, string secretName)
{
// this should point to your vault's URI, like https://<yourkeyvault>.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://<yourkeyvault>.vault.azure.net/secrets/Graph-App-Secret
Response<KeyVaultSecret> secret = client.GetSecretAsync(secretName).Result;
return secret.Value.Value;
}
- In your
Startup.cs
file, find theConfigureServices
method. Add the following code to call the GetSecretFromKeyVault method, right afterservices.AddAuthentication
. ℹ️ In the sample project, this code is present but commented out by default. Uncomment it.⚠️ Replace the string `ENTER_YOUR_SECRET_NAME_HERE` with the name of the client secret you entered into Azure Key Vault, for example `myClientSecret`.
// uncomment the following 3 lines to get ClientSecret from KeyVault
string tenantId = Configuration.GetValue<string>("AzureAd:TenantId");
services.Configure<MicrosoftIdentityOptions>(
options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "ENTER_YOUR_SECRET_NAME_HERE"); });
- Your
ConfigureServices
method should now look like the following snippet:
public void ConfigureServices(IServiceCollection services)
{
string[] initialScopes = Configuration.GetValue<string>("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<string>("AzureAd:TenantId");
services.Configure<MicrosoftIdentityOptions>(
options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "myClientSecret"); });
// ... more method code continues below
}
-
Add an environment variable to your App Service so your web app can find its key vault.
- Go to the Azure portal. Search for and select App Service, and then select your app.
- Select Configuration blade on the left, then select New Application Settings.
- 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/
-
Re-deploy your project to Azure App Service.
- Run the following command:
dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release
- 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.
-
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.
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.
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:
- Declaring that the client app is capable of handling claims challenges from the web API.
- Processing these challenges when thrown.
This sample declares that it's CAE-capable by adding a ClientCapabilities
property in the configuration, whose value is [ "cp1" ]
.
{
"AzureAd": {
// ...
// the following is required to handle Continuous Access Evaluation challenges
"ClientCapabilities": [ "cp1" ],
// ...
},
// ...
}
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:
-
Inject and instance of
MicrosoftIdentityConsentAndConditionalAccessHandler
in the controller constructor. The beginning of the HomeController becomes:public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly GraphServiceClient _graphServiceClient; private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler; private string[] _graphScopes = new[] { "user.read" }; public HomeController(ILogger<HomeController> 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<string>("DownstreamApi:Scopes")?.Split(' '); } // more code here
-
The process to handle CAE challenges from MS Graph comprises of the following steps:
- Catch a Microsoft Graph SDK's
ServiceException
and extract the requiredclaims
. This is done by wrapping the call to Microsoft Graph into a try/catch block that processes the challenge:
currentUser = await _graphServiceClient.Me.Request().GetAsync();
- Then redirect the user back to Azure AD with the new requested
claims
. Azure AD will use thisclaims
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.
- Catch a Microsoft Graph SDK's
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
.
- Microsoft identity platform (Azure Active Directory for developers)
- Overview of Microsoft Authentication Library (MSAL)
- Microsoft.Identity.Web
- Quickstart: Register an application with the Microsoft identity platform (Preview)
- Understanding Azure AD application consent experiences
- Understand user and admin consent
- Application and service principal objects in Azure Active Directory
- National Clouds
- Azure AD code samples
- Managed Identities for Azure resources
- Azure Key Vault
- Use Key Vault from App Service with Azure Managed Identity
- Authentication Scenarios for Azure AD.
Use Stack Overflow to get support from the community.
Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
Make sure that your questions or comments are tagged with [azure-active-directory
azure-ad-b2c
ms-identity
adal
msal
].
If you find a bug in the sample, raise the issue on GitHub Issues.
To provide feedback on or suggest features for Azure Active Directory, visit User Voice page.
If you'd like to contribute to this sample, see CONTRIBUTING.MD.
This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.