page_type | name | description | languages | products | urlFragment | extensions | |||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
sample |
Integrate a web app and Web Api that authenticates users and calls a custom Web API and Microsoft Graph using the multi-tenant integration pattern (SaaS) |
Integrate a web app and Web Api that authenticates users and calls a protected Web API and Microsoft Graph using the multi-tenant integration pattern (SaaS) |
|
|
microsoft-identity-platform-aspnetcore-webapp-tutorial |
|
Integrate a web app and Web Api that authenticates users and calls a custom Web API and Microsoft Graph using the multi-tenant integration pattern (SaaS)
- Overview
- Scenario
- Prerequisites
- Setup the sample
- Explore the sample
- Troubleshooting
- About the code
- How the code was created
- How to deploy this sample to Azure
- Next Steps
- Contributing
- Learn More
This sample demonstrates a ASP.NET Core Web App calling a ASP.NET Core Web API that is secured using Microsoft Entra ID.
ℹ️ To learn how to integrate an application with Microsoft Entra ID as a multi-tenant app, consider going through the recorded session:Develop multi-tenant applications with the Microsoft identity platform.
This sample demonstrates how to secure a multi-tenant ASP.NET Core MVC web application (TodoListClient) which calls another protected multi-tenant ASP.NET Core Web API (ToDoListService) with the Microsoft Identity Platform. This sample builds on the concepts introduced in the Integrate an app that authenticates users and calls Microsoft Graph using the multi-tenant integration pattern (SaaS) sample. We advise you go through that sample once before trying this sample.
In this sample, we would protect an ASP.Net Core Web API using the Microsoft Identity Platform. The Web API will be protected using Microsoft Entra ID OAuth 2.0 Bearer Authorization. The API will support authenticated users with Work and School accounts. Further on the API will also call a downstream API (Microsoft Graph) on behalf of the signed-in user using the OAuth 2.0 on-behalf-of flow to provide additional value to its client apps.
The Web API is marked as a multi-tenant app, so that it can be provisioned into Microsoft Entra tenants where the registered client applications in that tenant can then obtain Access Tokens for this web API and make calls to it.
Note that the client applications that want to call this web API do not need to be multi-tenant themselves to be able to do so.
This sample presents a client Web application that signs-in users and obtains an Access Token for this protected Web API.
Both applications use the Microsoft.Identity.Web and Microsoft Authentication Library MSAL.NET to sign-in user and obtain a JWT access token through the OAuth 2.0 protocol.
The client Web App:
- Signs-in users using the MSAL.NET and Microsoft.Identity.Web libraries.
- Acquires an Access Token for the protected Web API.
- Calls the ASP.NET Core Web API by using the access token as a bearer token in the authentication header of the Http request.
The Web API:
- Authorizes the caller (user) using the Microsoft.Identity.Web.
- Acquires another access token on-behalf-of the signed-in user using the on-behalf of flow.
- The Web API then uses this new Access token to call Microsoft Graph.
You can run the sample by using either Visual Studio or command line interface as shown below:
Clean the solution, rebuild the solution, and run it. You might want to go into the solution properties and set both projects as startup projects, with the service project starting first.
When you start the Web API from Visual Studio, depending on the browser you use, you'll get:
- an empty web page (with Microsoft Edge)
- or an error HTTP 401 (with Chrome)
This behavior is expected as the browser is not authenticated. The Web application will be authenticated, so it will be able to access the Web API.
A recording of a Microsoft Identity Platform developer session that covered this topic of developing a multi-tenant app with Microsoft Entra ID is available at Develop multi-tenant applications with Microsoft identity platform.
- Either Visual Studio or Visual Studio Code and .NET Core SDK
- An Microsoft Entra ID tenant. For more information, see: How to get a Microsoft Entra tenant
- A user account in your Microsoft Entra ID tenant.
This sample will not work with a personal Microsoft account. If you're signed in to the Microsoft Entra admin center with a personal Microsoft account and have not created a user account in your directory before, you will need to create one before proceeding.
From your shell or command line:
git clone https://github.com/Azure-Samples/microsoft-identity-platform-aspnetcore-webapp-tutorial.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.
cd 4-WebApp-your-API\4-3-AnyOrg\TodoListService
There are two projects in this sample. Each needs to be separately registered in your Microsoft Entra tenant. To register these projects, you can:
- follow the steps below for manually register your apps
- or use PowerShell scripts that:
- automatically creates the Microsoft Entra applications and related objects (passwords, permissions, dependencies) for you.
- modify the projects' configuration files.
Expand this section if you want to use this automation:
> :warning: If you have never used **Microsoft Graph PowerShell** before, we recommend you go through the [App Creation Scripts Guide](./AppCreationScripts/AppCreationScripts.md) once to ensure that your environment is prepared correctly for this step.
1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory
1. In PowerShell run:
```PowerShell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
```
1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly.
1. For interactive process -in PowerShell, run:
```PowerShell
cd .\AppCreationScripts\
.\Configure.ps1 -TenantId "[Optional] - your tenant id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'"
```
> Other ways of running the scripts are described in [App Creation Scripts guide](./AppCreationScripts/AppCreationScripts.md). The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios.
To manually register the apps, as a first step you'll need to:
- Sign in to the Microsoft Entra admin center.
- If your account is present in more than one Microsoft Entra 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 Microsoft Entra tenant.
- Navigate to the Microsoft Entra admin center and select the Microsoft Entra ID 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
WebApi_MultiTenant_v2
. - Under Supported account types, select Accounts in any organizational directory
- Select Register to create the application.
- In the Name section, enter a meaningful application name that will be displayed to users of the app, for example
- In the Overview blade, find and note the Application (client) ID. You use this value in your app's configuration file(s) later in your code.
- In the app's registration screen, select the Authentication blade to the left.
- If you don't have a platform added, select Add a platform and select the Web option.
- In the Redirect URI section enter the following redirect URI:
https://localhost:44351/api/Home
- Click Save to save your changes.
- In the Redirect URI section enter the following redirect URI:
- In the app's registration screen, select the Certificates & secrets blade in the left to open the page where you can generate secrets and upload certificates.
- 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 Microsoft Entra admin center before navigating to any other screen or blade.
💡 For enhanced security, instead of using client secrets, consider using certificates and Azure KeyVault.
- Since this app signs-in users, we will now proceed to select delegated permissions, which is is required by apps signing-in users.
- 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 User.Read.All in the list. Use the search box if necessary. This permission requires Admin Consent, so please select Grant admin consent.
- Select the Add permissions button at the bottom.
- Type a key description (for instance
- In the app's registration screen, select the Expose an API blade to the left to open the page where you can publish the permission as an API for which client applications can obtain access tokens for. The first thing that we need to do is to declare the unique resource URI that the clients will be using to obtain access tokens for this API. To declare an resource URI(Application ID URI), follow the following steps:
- Select Set next to the Application ID URI to generate a URI that is unique for this app.
- For this sample, accept the proposed Application ID URI (
api://{clientId}
) by selecting Save.ℹ️ Read more about Application ID URI at Validation differences by supported account types (signInAudience).
- All APIs must publish a minimum of one scope, also called Delegated Permission, for the client apps to obtain an access token for a user successfully. To publish a scope, follow these steps:
- Select Add a scope button open the Add a scope screen and Enter the values as indicated below:
- For Scope name, use
ToDoList.Read
. - Select Admins and users options for Who can consent?.
- For Admin consent display name type in Read users ToDo list using the 'WebApi_MultiTenant_v2'.
- For Admin consent description type in Allow the app to read the user's ToDo list using the 'WebApi_MultiTenant_v2'.
- For User consent display name type in Read your ToDo list items via the 'WebApi_MultiTenant_v2'.
- For User consent description type in Allow the app to read your ToDo list items via the 'WebApi_MultiTenant_v2'.
- Keep State as Enabled.
- Select the Add scope button on the bottom to save this scope.
Repeat the steps above for another scope named ToDoList.ReadWrite
- For Scope name, use
- Select the Manifest blade on the left.
- Set
accessTokenAcceptedVersion
property to 2. - Select on Save.
- Set
ℹ️ Follow the principle of least privilege when publishing permissions for a web API.
-
All APIs should publish a minimum of one App role for applications, also called Application Permission, for the client apps to obtain an access token as themselves, i.e. when they are not signing-in a user. Application permissions are the type of permissions that APIs should publish when they want to enable client applications to successfully authenticate as themselves and not need to sign-in users. To publish an application permission, follow these steps:
-
Still on the same app registration, select the App roles blade to the left.
-
Select Create app role:
- For Display name, enter a suitable name for your application permission, for instance ToDoList.Read.All.
- For Allowed member types, choose Application to ensure other applications can be granted this permission.
- For Value, enter ToDoList.Read.All.
- For Description, enter Allow the app to read every user's ToDo list using the 'WebApi_MultiTenant_v2'.
- Select Apply to save your changes.
Repeat the steps above for another app permission named ToDoList.ReadWrite.All
- Still on the same app registration, select the Token configuration blade to the left.
- Select Add optional claim:
- Select optional claim type, then choose Access.
- Select the optional claim idtyp.
Indicates token type. This claim is the most accurate way for an API to determine if a token is an app token or an app+user token. This is not issued in tokens issued to users.
- Select Add to save your changes.
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
ToDoListService\appsettings.json
file. - Find the key
Domain
and replace the existing value with your Microsoft Entra tenant domain, ex.contoso.onmicrosoft.com
. - Find the key
TenantId
and replace the existing value with 'common'. - Find the key
ClientId
and replace the existing value with the application ID (clientId) ofWebApi_MultiTenant_v2
app copied from the Microsoft Entra admin center. - Find the key
ClientSecret
and replace the existing value with the generated secret that you saved during the creation ofWebApi_MultiTenant_v2
copied from the Microsoft Entra admin center.
- Navigate to the Microsoft Entra admin center and select the Microsoft Entra ID 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_MultiTenant_v2
. - Under Supported account types, select Accounts in any organizational directory
- Select Register to create the application.
- In the Name section, enter a meaningful application name that will be displayed to users of the app, for example
- In the Overview blade, find and note the Application (client) ID. You use this value in your app's configuration file(s) later in your code.
- In the app's registration screen, select the Authentication blade to the left.
- If you don't have a platform added, select Add a platform and select the Web option.
- In the Redirect URI section enter the following redirect URIs:
https://localhost:44321/
https://localhost:44321/signin-oidc
- In the Front-channel logout URL section, set it to
https://localhost:44321/signout-callback-oidc
. - Click Save to save your changes.
- In the Redirect URI section enter the following redirect URIs:
- In the app's registration screen, select the Certificates & secrets blade in the left to open the page where you can generate secrets and upload certificates.
- 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 Microsoft Entra admin center before navigating to any other screen or blade.
💡 For enhanced security, instead of using client secrets, consider using certificates and Azure KeyVault.
- Since this app signs-in users, we will now proceed to select delegated permissions, which is is required by apps signing-in users.
- 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 My APIs tab is selected.
- In the list of APIs, select the API
WebApi_MultiTenant_v2
. - In the Delegated permissions section, select ToDoList.Read, ToDoList.ReadWrite in the list. Use the search box if necessary.
- Select the Add permissions button at the bottom.
- Type a key description (for instance
- Still on the same app registration, select the Token configuration blade to the left.
- Select Add optional claim:
- Select optional claim type, then choose ID.
- Select the optional claim acct.
Provides user's account status in tenant. If the user is a member of the tenant, the value is 0. If they're a guest, the value is 1.
- Select Add to save your changes.
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
ToDoListClient\appsettings.json
file. - Find the key
ClientId
and replace the existing value with the application ID (clientId) ofWebApp_MultiTenant_v2
app copied from the Microsoft Entra admin center. - Find the key
TenantId
and replace the existing value with 'common'. - Find the key
Domain
and replace the existing value with your Microsoft Entra tenant domain, ex.contoso.onmicrosoft.com
. - Find the key
ClientSecret
and replace the existing value with the generated secret that you saved during the creation ofWebApp_MultiTenant_v2
copied from the Microsoft Entra admin center. - Find the key
RedirectUri
and replace the existing value with the base address ofWebApp_MultiTenant_v2
(by defaulthttps://localhost:44321/
). - Find the key
TodoListServiceScope
and replace the existing value with ScopeDefault. - Find the key
TodoListServiceAppId
and replace the existing value with the application ID (clientId) ofWebApi_MultiTenant_v2
app copied from the Microsoft Entra admin center. - Find the key
TodoListBaseAddress
and replace the existing value with the base address ofWebApi_MultiTenant_v2
(by defaulthttps://localhost:44351/
). - Find the key
AdminConsentRedirectApi
and replace the existing value with the Redirect URI forWebApi_MultiTenant_v2
. (by defaulthttps://localhost:44351/
). - Find the app key
ClientCertificates
and add the keys as displayed below:
"ClientCertificates": [
{
"SourceType": "",
"CertificateDiskPath": "",
"CertificatePassword": ""
}
]
- Update values of the keys:
1 .
SourceType
toPath
.CertificateDiskPath
to the path where certificate exported with private key (the name will be assigned automatically by PowerShell script and it will be equal to the Application name.pfx) is stored. For example,C:\\AppCreationScripts\the name will be assigned automatically by PowerShell script and it will be equal to the Application name.pfx
CertificatePassword
add the password used while exporting the certificate.
- If you had set
ClientSecret
previously, set its value to empty string,""
.
For a middle-tier web API (WebApi_MultiTenant_v2
) to be able to call a downstream web API, the middle tier app needs to be granted the required permissions as well. However, since the middle-tier cannot interact with the signed-in user, it needs to be explicitly bound to the client app in its Microsoft Entra ID registration. This binding merges the permissions required by both the client and the middle-tier web API and presents it to the end user in a single consent dialog. The user then consent to this combined set of permissions. To achieve this, you need to add the Application Id of the client app to the knownClientApplications
property in the manifest of the web API. Here's how:
- In the Microsoft Entra admin center, navigate to your
WebApi_MultiTenant_v2
app registration, and select the Manifest blade. - In the manifest editor, change the
knownClientApplications: []
line so that the array contains the Client ID of the client application (WebApp_MultiTenant_v2
) as an element of the array.
For instance:
"knownClientApplications": ["ca8dca8d-f828-4f08-82f5-325e1a1c6428"],
- Save the changes to the manifest.
Follow README-use-certificate.md to know how to use this option.
From your shell or command line, execute the following commands:
cd 4-WebApp-your-API\4-3-AnyOrg\TodoListService\TodoListService
dotnet run
Then, open a separate command terminal and run:
cd 4-WebApp-your-API\4-3-AnyOrg\ToDoListClient
dotnet run
Expand the section
Open your browser and navigate to https://localhost:44321
.
NOTE: Remember, the To-Do list is stored in memory in this
ToDoListService
app. Each time you run the projects, your To-Do list will get emptied.
To properly test this application, you need at least two tenants, and on each tenant, at least one administrator and one non-administrator account.
A service principal of your multi-tenant app and API is provisioned after the tenant admin manually or programmatically consents. The consent can be obtained from a tenant admin by using one of the following methods:
- By using the /adminconsent endpoint.
- By Using the PowerShell command New-AzADServicePrincipal.
You can try the /adminconsent endpoint on the home page of the sample by clicking on the Consent as Admin
link. Web API is provisioned first because the Web App is dependent on the Web API. The admin consent endpoint allows developers to programmatically build links to obtain consent.
The
.default
scopeDid you notice the scope here is set to
.default
, as opposed toUser.Read.All
for Microsoft Graph andaccess_as_user
for Web API? This is a built-in scope for every application that refers to the static list of permissions configured on the application registration. Basically, it bundles all the permissions in one scope. The/.default
scope can be used in any OAuth 2.0 flow, but is necessary when using the v2 admin consent endpoint to request application permissions. Read aboutscopes
usage at Scopes and permissions in the Microsoft Identity Platform.
Since both the web app and API needs to be consented by the tenant admin, the admin will need to consent twice.
- First, the tenant admin will consent for the Web API. The Web API is consented first as the client Web app depends on the Web API and not the other way around.
- Then, the code will redirect the tenant admin to consent for the client web app.
When redirected to the /adminconsent
endpoint, the tenant admin will see the sign-in or the coose account screen:
After you choose an admin account, it will lead to the following prompt to consent for the Web API :
When you click Accept
, it will redirects to /adminconsent
endpoint again to obtain consent for the Web App:
After you choose an admin account, it will lead to the Web App consent as below:
Once it finishes, your applications service principals will be provisioned in the tenant admin's tenant.
The tenant administrators of a tenant can provision service principals for the applications in their tenant using the Azure AD Powershell Module. After installing the Azure AD Powershell Module v2, you can run the following cmdlet:
Connect-AzureAD -TenantId "[The tenant Id]"
New-AzureADServicePrincipal -AppId '<client/app id>'
If you get errors during admin consent, consider deleting the service principal of your apps in the tenant(s) you are about to test, in order to remove any previously granted consent and to be able to run the provisioning process from the beginning.
Steps for deleting a service principal differs with respect to whether the principal is in the home tenant of the application or in another tenant. If it is in the home tenant, you will find the entry for the application under the App Registrations blade. If it is another tenant, you will find the entry under the Enterprise Applications blade. Read more about these blades in the How and why applications are added to Microsoft Entra ID.The screenshot below shows how to access the service principal from a home tenant:
The rest of the process is the same for both cases. In the next screen, click on Properties and then the Delete button on the upper side.
You have now deleted the service principal of Web App for that tenant. Similarly, you can delete the service principal for Web API. Next time, admin needs to provision service principal for both the applications in the tenant from which that admin belongs.
- Open your browser and navigate to
https://localhost:44321
and sign-in using the link on top-right. - Click on
To-Do List
, you can click onCreate New
link. It will redirect to create task screen where you can add a new task and assign it to any user from the list. - The
To-Do List
screen also displays tasks that are assigned to and created by signed-in user. The user can edit and delete the created tasks but can only view the assigned tasks.
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.
Expand for troubleshooting info
ASP.NET core applications create session cookies that represent the identity of the caller. Some Safari users using iOS 12 had issues which are described in ASP.NET Core #4467 and the Web kit bugs database Bug 188165 - iOS 12 Safari breaks ASP.NET Core 2.1 OIDC authentication.
If your web site needs to be accessed from users using iOS 12, you probably want to disable the SameSite protection, but also ensure that state changes are protected with CSRF anti-forgery mechanism. See the how to fix section of Microsoft Security Advisory: iOS12 breaks social, WSFed and OIDC logins #4647
To provide feedback on or suggest features for Microsoft Entra ID, visit User Voice page.
Expand the section
### Provisioning your Multi-tenant Apps in another Microsoft Entra tenant programmaticallyOften the user-based consent will be disabled in a Microsoft Entra tenant or your application will be requesting permissions that requires a tenant-admin consent. In these scenarios, your application will need to utilize the /adminconsent
endpoint to provision both the ToDoListClient and the ToDoListService before the users from that tenant are able to sign-in to your app.
When provisioning, you have to take care of the dependency in the topology where the ToDoListClient is dependent on ToDoListService. So in such a case, you would provision the ToDoListService before the ToDoListClient.
In Startup.cs
, below lines of code enables Microsoft identity platform endpoint. This endpoint is capable of signing-in users both with their Work and School Accounts.
services.AddMicrosoftWebAppAuthentication(Configuration)
.AddMicrosoftWebAppCallsWebApi(Configuration, new string[] { Configuration["TodoList:TodoListServiceScope"] })
.AddInMemoryTokenCaches();
AddMicrosoftWebAppAuthentication
: This enables your application to use the Microsoft identity platform endpoint. This endpoint is capable of signing-in users both with their Work and School and Microsoft Personal accounts.AddMicrosoftWebAppCallsWebApi
: Enables the web app to call the protected API ToDoList Api.AddInMemoryTokenCaches
: Adds an in memory token cache provider, which will cache the Access Tokens acquired for the Web API.
The following code enables to add client service to use the HttpClient by dependency injection.
services.AddTodoListService(Configuration);
In HomeController.cs
, the method AdminConsentApi
has the code to redirect the user to the admin consent endpoint for the admin to consent for the Web API. The state parameter in the URI contains a link for AdminConsentClient
method.
public IActionResult AdminConsentApi()
{
string adminConsent1 = "https://login.microsoftonline.com/organizations/v2.0/adminconsent?client_id="+ _ApiClientId
+ "&redirect_uri=" + _ApiRedirectUri
+ "&state=" + _RedirectUri + "Home/AdminConsentClient" + "&scope=" + _ApiScope;
return Redirect(adminConsent1);
}
The method AdminConsentClient
has the code to redirect the user to the admin consent endpoint for the admin to consent for the Web App.
public IActionResult AdminConsentClient()
{
string adminConsent2 = "https://login.microsoftonline.com/organizations/v2.0/adminconsent?client_id=" + _ClientId
+ "&redirect_uri=" + _RedirectUri
+ "&state=123&scope=" + _TodoListServiceScope;
return Redirect(adminConsent2);
}
If signed-in user does not have consent for a permission on the Web API, for instance "user.read.all" in this sample, then Web API will throw MsalUiRequiredException
. The response contains the details about consent Uri and proposed action.
The Web App contains a method HandleChallengeFromWebApi
in ToDoListService.cs
that handles the exception thrown by API. It creates a consent URI and throws a custom exception i.e., WebApiMsalUiRequiredException
.
private void HandleChallengeFromWebApi(HttpResponseMessage response)
{
//proposedAction="consent"
List<string> result = new List<string>();
AuthenticationHeaderValue bearer = response.Headers.WwwAuthenticate.First(v => v.Scheme == "Bearer");
IEnumerable<string> parameters = bearer.Parameter.Split(',').Select(v => v.Trim()).ToList();
string proposedAction = GetParameter(parameters, "proposedAction");
if (proposedAction == "consent")
{
string consentUri = GetParameter(parameters, "consentUri");
var uri = new Uri(consentUri);
var queryString = System.Web.HttpUtility.ParseQueryString(uri.Query);
queryString.Set("redirect_uri", _ApiRedirectUri);
queryString.Add("prompt", "consent");
queryString.Add("state", _RedirectUri);
var uriBuilder = new UriBuilder(uri);
uriBuilder.Query = queryString.ToString();
var updateConsentUri = uriBuilder.Uri.ToString();
result.Add("consentUri");
result.Add(updateConsentUri);
throw new WebApiMsalUiRequiredException(updateConsentUri);
}
}
The following code in ToDoListController.cs
catches the WebApiMsalUiRequiredException
exception thrown by HandleChallengeFromWebApi
method as explained above. Further it Redirects to consentUri
that is retrieved from exception message. Admin needs to consent as user.read.all
permission requires admin approval.
public async Task<IActionResult> Create()
{
ToDoItem todo = new ToDoItem();
try
{
...
}
catch (WebApiMsalUiRequiredException ex)
{
return Redirect(ex.Message);
}
}
In HomeController.cs, the method AdminConsent
redirects to the URI passed in the state parameter by Web App. If admin consent is cancelled from API consent screen then it redirects to base address of Web App.
public IActionResult AdminConsent()
{
var decodeUrl = System.Web.HttpUtility.UrlDecode(HttpContext.Request.QueryString.ToString());
var queryString = System.Web.HttpUtility.ParseQueryString(decodeUrl);
var clientRedirect = queryString["state"];
if (!string.IsNullOrEmpty(clientRedirect))
{
if (queryString["error"] == "access_denied" && queryString["error_subcode"] == "cancel")
{
var clientRedirectUri = new Uri(clientRedirect);
return Redirect(clientRedirectUri.GetLeftPart(System.UriPartial.Authority));
}
else
{
return Redirect(clientRedirect);
}
}
else
{
return RedirectToAction("GetTodoItems", "TodoList");
}
}
This sample exposes a delegated permission (access_as_user) that will be presented in the access token claim. The method AddMicrosoftWebApi
does not validate the scope, but Microsoft.Identity.Web has a HttpContext extension method, VerifyUserHasAnyAcceptedScope
, where you can validate the scope as below:
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
For delegated permissions how to access scopes
If a token has delegated permission scopes, they will be in the scp
or http://schemas.microsoft.com/identity/claims/scope
claim.
By marking your application as multi-tenant, your application will be able to sign-in users from any Microsoft Entra tenant out there. Now you would want to restrict the tenants you want to work with. For this, we will now extend token validation to only those Microsoft Entra tenants registered in the application database. Below, the event handler OnTokenValidated
was configured to grab the tenantId
from the token claims and check if it has an entry on the records. If it doesn't, an exception is thrown, canceling the authentication.
Another way to control who is allowed into API is to use Policies. This is configured as part of services.AddAuthorization call. See the code below.
//get list of allowed tenants from configuration
var allowedTenants = Configuration.GetSection("AzureAd:AllowedTenants").Get<string[]>();
//configure OnTokenValidated event to filter the tenants
//you can use either this approach or the one below through policies
services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingOnTokenValidatedHandler(context);
if (!allowedTenants.Contains(context.Principal.GetTenantId()))
{
throw new UnauthorizedAccessException("This tenant is not authorized");
}
};
});
// Creating policies that wraps the authorization requirements
services.AddAuthorization(
//uncomment this part if you need to filter the tenants by a policy
//refer to https://github.com/AzureAD/microsoft-identity-web/wiki/authorization-policies#filtering-tenants
//builder =>
//{
// string policyName = "User belongs to a specific tenant";
// builder.AddPolicy(policyName, b =>
// {
// b.RequireClaim(ClaimConstants.TenantId, allowedTenants);
// });
// builder.DefaultPolicy = builder.GetPolicy(policyName);
//}
);
During startup of Web API Application, four permissions were created:
- 2 for user scopes: ToDoList.Read and ToDoList.ReadWrite.
- 2 for app permissions: ToDoList.Read.All and ToDoList.ReadWrite.All It's important to note that because current sample is a multi-tenant sample, app permissions won't take effect, but are left here as an example for a single tenant samples
For enhanced and secure access we can decide what scope can access what operation. For example Read and Write scopes and permissions are required for GET:
// GET: api/TodoItems
[HttpGet]
[RequiredScopeOrAppPermission(
AcceptedScope = new string[] { _todoListReadScope, _todoListReadWriteScope },
AcceptedAppPermission = new string[] { _todoListReadAllPermission, _todoListReadWriteAllPermission }
)]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
try
{
// this is a request for all ToDo list items of a certain user.
if (!IsAppOnlyToken())
{
return await _context.TodoItems.Where(x => x.TenantId == _userTenantId && (x.AssignedTo == _signedInUser || x.Assignedby == _signedInUser)).ToArrayAsync();
}
// Its an app calling with app permissions, so return all items across all users
return await _context.TodoItems.Where(x => x.TenantId == _userTenantId).ToArrayAsync();
}
catch (Exception)
{
throw;
}
}
Write scopes and permissions will let user access POST:
[HttpPost]
[RequiredScopeOrAppPermission(
AcceptedScope = new string[] { _todoListReadWriteScope },
AcceptedAppPermission = new string[] { _todoListReadWriteAllPermission })]
public async Task<ActionResult<TodoItem>> CreateTodoItem(TodoItem todoItem)
{
var random = new Random();
todoItem.Id = random.Next();
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return Ok(todoItem);
}
Expand the section
The sample is based on ASP.NET CORE API template
Because there are two parts - Client and Service, you will have to create 2 separate projects under same solution.
During the project configuration, specify Microsoft Identity Platform
inside Authentication Type
dropdown box. As IDE installs the solution, it might require to install an additional components.
After the initial project was created, we have to continue with further configuration and tweaking. The most of configuration changes are inside Setup.cs files, so please follow with Client Setup.cs and Service Setup.cs for further details.
You will have to delete the default controllers and all relevant data from the projects and create Home and TodoList controller for bot Client and Service projects. Refer to the controller sections accordingly.
Expand the section
There is one web API 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
⚠️ Please make sure that you have not switched on the Automatic authentication provided by App Service. It interferes the authentication code used in this code example.
Follow the link to Publish with Visual Studio.
- Install the Visual Studio Code extension Azure App Service.
- Follow the link to Publish with Visual Studio Code
ℹ️ When calling the web API, your app may receive an error similar to the following:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://some-url-here. (Reason: additional information here).
If that's the case, you'll need enable cross-origin resource sharing (CORS) for you web API. Follow the steps below to do this:
- Go to Microsoft Entra admin center, and locate the web API project that you've deployed to App Service.
- On the API blade, select CORS. Check the box Enable Access-Control-Allow-Credentials.
- Under Allowed origins, add the URL of your published web app that will call this web API.
- Navigate back to to the Microsoft Entra admin center. In the left-hand navigation pane, select the Microsoft Entra ID service, and then select App registrations (Preview).
- In the resulting screen, select the
WebApi_MultiTenant_v2
application. - In the app's registration screen, select Authentication in the menu.
- In the Redirect URIs section, update any absolute reply URLs to match the site URL of your Azure deployment. Relative URL should be left as-is. For example:
https://WebApi_MultiTenant_v2.azurewebsites.net/api/Home
- In the Redirect URIs section, update any absolute reply URLs to match the site URL of your Azure deployment. Relative URL should be left as-is. For example:
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.
- Install the Visual Studio Code extension Azure App Service.
- Follow the link to Publish with Visual Studio Code
- Navigate back to to the Microsoft Entra admin center. In the left-hand navigation pane, select the Microsoft Entra ID service, and then select App registrations (Preview).
- In the resulting screen, select the
WebApp_MultiTenant_v2
application. - In the app's registration screen, select Authentication in the menu.
- In the Redirect URIs section, update the reply URLs to match the site URL of your Azure deployment. For example:
https://WebApp_MultiTenant_v2.azurewebsites.net/
https://WebApp_MultiTenant_v2.azurewebsites.net/signin-oidc
- Update the Front-channel logout URL fields with the address of your service, for example https://WebApp_MultiTenant_v2.azurewebsites.net
- In the Redirect URIs section, update the reply URLs to match the site URL of your Azure deployment. For example:
- In your IDE, locate the
WebApp_MultiTenant_v2
project. Then, openToDoListClient\appsettings.json
. - Find the key for redirect URI and replace its value with the address of the web app you published, for example, https://WebApp_MultiTenant_v2.azurewebsites.net/redirect.
- Find the key for web API endpoint and replace its value with the address of the web API you published, for example, https://WebApi_MultiTenant_v2.azurewebsites.net/api.
⚠️ If your app is using an 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 be empty. 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.
Learn how to:
- Change your app to sign-in users from any organization or Microsoft accounts
- Enable users from National clouds to sign-in to your application
- Enable your web app to call a web API on behalf of the signed-in user
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.
- Microsoft identity platform (Microsoft Entra ID for developers)
- Microsoft Entra ID code samples
- Overview of Microsoft Authentication Library (MSAL)
- Register an application with the Microsoft identity platform
- Configure a client application to access web APIs
- Understanding Microsoft Entra application consent experiences
- Understand user and admin consent
- Application and service principal objects in Microsoft Entra ID
- Authentication Scenarios for Microsoft Entra ID
- Building Zero Trust ready apps
- National Clouds
- Microsoft.Identity.Web
- Converting an application to Multi-tenant
- Multi-tenant SaaS database tenancy patterns
- How to configure a new multi-tenant application
- How to: Sign in any Microsoft Entra ID user using the multi-tenant application pattern
- Add a multitenant application to the Microsoft Entra application gallery
- Permissions and Consent
- Validating Access Tokens
- User and application tokens
- Validation differences by supported account types
- How to manually validate a JWT access token using the Microsoft identity platform