- Create a REST API that is protected by Azure AD
- Create an Azure AD application for your REST API
- Create static permissions for your REST Api during application registration
- Gain consent for the middle-tier application
- Integrate authorization in Swagger for your API
- Use the OAuth2 auth code grant to acquire an access token from a client to call your REST API
At a high level, the entire authentication flow for a native/mobile/webapp looks a bit like this:
To protect an API with Azure AD an Azure AD application for the API must be created. An API does not sign in users, therefore no reply url must be created when the Azure AD application is registered.
A sample scenario is already implemnted for this challenge. The sample contains a web API that is running on ASP.NET Core which is protected by Azure AD and calls Microsoft-Graph API on behalf of signed in user to read user's profile. The web API is accessed by an ASP.NET Core web application on behalf of the signed-in user. The ASP.NET Core Web application uses the OpenID Connect middleware and MSAL for .NET to acquire an access token for the API on behalf of the signed-in user. The sources for the sample application is located here: aspnetcore-protect-api
To grant consent for the middle-tier API the scope /.default can be used. This is a built-in scope for every application that refers to the static list of permissions configured on the application registration. If you need further information about the /.default scope take a look here.
The setting "knownClientApplication" of an Azure AD application is used for bundling consent if you have a solution that contains two parts: a client app and a custom web API app. If you enter the appID of the client app into this value, the user will only have to consent once to the client app. Azure AD will know that consenting to the client means implicitly consenting to the web API and will automatically provision service principals for both the client and web API at the same time. Both the client and the web API app must be registered in the same tenant.
To create the Azure AD applications we use Powershell and the AzureAD module. The API is running on port 5002 and the Web application is running on port 5003.
Connect to AzureAd Instance:
Import-Module AzureAd
Connect-AzureAD
To expose an API application in AzureAD OAuth 2.0 permission scopes must be exposed. When creating an Azure AD application using New-AzureADApplication Cmdlet a scope named "user_impersonation" is created automatically. If further scopes must be created, use the following:
$exposedScopes = New-Object -TypeName Microsoft.Open.AzureAD.Model.OAuth2Permission
$exposedScopes.Type = "User"
$exposedScopes.AdminConsentDisplayName = "AccessEcho Claims API"
$exposedScopes.AdminConsentDescription = "Access Echo Claims API on behalf of signed-in users to echo claims."
$exposedScopes.Id = $(New-Guid)
$exposedScopes.IsEnabled = $true
$exposedScopes.Value = "myscope"
$exposedScopes.UserConsentDisplayName = $null
$exposedScopes.UserConsentDescription = $null
Configure required resource access to Microsoft Graph API User.Read
$requiredResourceAccess = New-Object -TypeName Microsoft.Open.AzureAD.Model.RequiredResourceAccess
# Microsoft Graph API has id '00000003-0000-0000-c000-000000000000'
$requiredResourceAccess.ResourceAppId = "00000003-0000-0000-c000-000000000000"
$requiredResourceAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
$resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
# Scope User.Read has id 'e1fe6dd8-ba31-4d61-89e7-88639da4683d'
$resourceAccess.Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
$resourceAccess.Type = "Scope"
$requiredResourceAccess.ResourceAccess.Add($resourceAccess)
Create the Azure AD application for the API.
$api = New-AzureADApplication -DisplayName "MiddleTierAPI" -IdentifierUris "https://middletierapi" -RequiredResourceAccess $requiredResourceAccess
Do the following when further scopes must be created.
$api = New-AzureADApplication -DisplayName "MiddleTierAPI" -IdentifierUris "https://middletierapi" -Oauth2Permissions $exposedScopes -RequiredResourceAccess $requiredResourceAccess
To acquire an access token for the Microsoft Graph API we use the OAuth2 on-behalf-of flow, therefore we need a client secret.
$secretapi = New-Guid
New-AzureADApplicationPasswordCredential -ObjectId $api.ObjectId -CustomKeyIdentifier "ClientSecret" -Value $secretapi
Create a ServicePrincipal for the application
New-AzureADServicePrincipal -AppId $api.AppId
To access the API in the web application an AzureAD application must be registered with required resource access right to access the API.
# required resource access (the API)
$requiredResourceAccess = New-Object -TypeName Microsoft.Open.AzureAD.Model.RequiredResourceAccess
$requiredResourceAccess.ResourceAppId = $api.AppId
$requiredResourceAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
$resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
$resourceAccess.Id = $api.Oauth2Permissions[0].Id
$resourceAccess.Type = "Scope"
$requiredResourceAccess.ResourceAccess.Add($resourceAccess)
# Create the Azure AdApplication
$app = New-AzureADApplication -DisplayName "MiddleTierWebApp" -IdentifierUris "https://middletierwebapp" -ReplyUrls "http://localhost:5003/signin-oidc" -RequiredResourceAccess $requiredResourceAccess
To acquire an access token for the API we use the OAuth2 code grant flow, therefore we need a client secret.
$secret = New-Guid
New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId -CustomKeyIdentifier "ClientSecret" -Value $secret
Create a ServicePrincipal for the application
New-AzureADServicePrincipal -AppId $app.AppId
$api.KnownClientApplications = New-Object System.Collections.Generic.List[System.String]
$api.KnownClientApplications.Add($app.AppId)
Set-AzureADApplication -ObjectId $api.ObjectId -KnownClientApplications $api.KnownClientApplications
In the demo application the ASP.NET Core web application calls the ASP.NET Core API which just returns the claims of the calling user. The demo is located here To run the demo application two shells must be opened, one for the web application and one for the API.
Edit the file appsettings.json and replace Domain, TenantId, and ClientId.
In the shell navigate to the folder /apps/aspnetcore-protect-api/WebApi and run the following dotnet command
dotnet run
The API is listening on port 5002.
Edit the file appsettings.json and replace Domain, TenantId, ClientId and ClientSecret.
In another shell navigate to the folder /apps/apsnetcore-protect-api/WebApplication and run the following dotnet command.
dotnet run
The web application is listening on port 5003.