services | platforms | author | level | client | service | endpoint |
---|---|---|---|---|---|---|
active-directory |
dotnet |
jmprieur |
200 |
ASP.NET Core Web App |
Microsoft Graph |
Microsoft identity platform |
This sample shows how to update your ASP.NET Core Web API so that it now calls other Microsoft APIs than Microsoft Graph. The sample now calls the Azure Resource Manager API as well as the Azure Storage
To run this sample:
Pre-requisites:
This is the third phase of the tutorial. It's recommended that you have gone through the previous phases of the tutorial, in particular how the WebApp signs-in users with Microsoft Identity (OIDC) / with work and school or personal accounts and Web app calls the Microsoft Graph API on behalf of a user signing-in.
This chapter shows the incremental changes required to call two Microsoft APIs other than Microsoft Graph (Azure Resource Management and Azure Storage).
You first need to register your app as described in the first phase of the tutorial
Then, the follow the following extra set of steps:
- In the API permissions section for the application, notice that a delegated permission is set by default to Microsoft Graph for the scope User.Read
- Select Add a permission
- In the Microsoft APIs tab, select Azure Service Management
- Check user_impersonation
- Select Add permissions
- Select Add a permission
- In the Microsoft APIs tab, select Azure Storage. If you cannot find it in this category, try in the APIs my organization uses tab
- Check user_impersonation
- Select Add permissions
For the Azure Storage preparation see Authenticate with Azure Active Directory from an application for access to blobs and queues
If you have not already, clone this sample from your shell or command line:
git clone https://github.com/Azure-Samples/microsoft-identity-platform-aspnetcore-webapp-tutorial webapp
cd webapp
cd "3-WebApp-multi-APIs"
In the appsettings.json
file, replace, if you have not already:
- the
ClientID
value with the Application ID from the application you registered in Application Registration portal, - the
TenantId
bycommon
, as here you chose to sign-in users with their work or school or personal account. In case you want to sign-in different audiences, refer back to the first phase of the tutorial - and the
ClientSecret
by the client secret you generated in Step 1.
-
Build the solution and run it.
-
Open your web browser and make a request to the app. The app immediately attempts to authenticate you via the Microsoft identity platform endpoint. Sign in with your personal account or with a work or school account.
-
Go to the Contacts page, you should now see all kind of information about yourself (a call was made to the Microsoft Graph me endpoint)
Starting from the previous phase of the tutorial, the code was incrementally updated with the following steps:
After the following lines in the ConfigureServices(IServiceCollection services) method, after services.AddMicrosoftIdentityPlatformAuthentication(Configuration);
, add services.AddHttpClient<IArmOperations, ArmApiOperationService>();
:
public void ConfigureServices(IServiceCollection services)
{
. . .
// Token acquisition service based on MSAL.NET
// and chosen token cache implementation
services.AddMicrosoftIdentityPlatformAuthentication(Configuration)
.AddMsal(new string[] { Constants.ScopeUserRead })
.AddInMemoryTokenCache();
services.AddHttpClient<IArmOperations, ArmApiOperationService>();
This enables to add the Azure Resource manager micro-service to use the HttpClient by dependency injection.
The Services\ARM
sub folder which is a simple wrapper against the ARM REST API.
In the Views\Home
folder add a view named Tenants.cshtml
@using System
@using System.Collections.Generic
@{
ViewData["Title"] = "Tenants";
IDictionary<string, string> tenants = ViewData["tenants"] as IDictionary<string, string>;
if (tenants == null)
{
tenants = (ViewData["tenants"] as IEnumerable<string>).ToDictionary(name => name);
}
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<table class="table table-striped table-condensed" style="font-family: monospace">
<tr>
<th>Tenant ID</th>
<th>Tenant name</th>
</tr>
@foreach(var tenant in tenants)
{
<tr>
<td>@tenant.Key</td>
<td>@tenant.Value</td>
</tr>
}
</table>
// Requires that the app has added the Azure Service Management / user_impersonation scope, and that
// the admin tenant does not require admin consent for ARM.
[AuthorizeForScopes(Scopes = new[] { "https://management.azure.com/user_impersonation"})]
public async Task<IActionResult> Tenants()
{
var accessToken =
await tokenAcquisition.GetAccessTokenOnBehalfOfUser(HttpContext,
new[] { $"{ArmApiOperationService.ArmResource}user_impersonation" });
var tenantIds = await armOperations.EnumerateTenantsIdsAccessibleByUser(accessToken);
ViewData["tenants"] = tenantIds;
return View();
}
[AuthorizeForScopes(Scopes = new[] { "https://storage.azure.com/user_impersonation" })]
public async Task<IActionResult> Blob()
{
var scopes = new string[] { "https://storage.azure.com/user_impersonation" };
var accessToken =
await tokenAcquisition.GetAccessTokenOnBehalfOfUser(HttpContext, scopes);
// create a blob on behalf of the user
TokenCredential tokenCredential = new TokenCredential(accessToken);
StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
// replace the URL below with your storage account URL
Uri blobUri = new Uri("https://blobstorageazuread.blob.core.windows.net/sample-container/Blob1.txt");
CloudBlockBlob blob = new CloudBlockBlob(blobUri, storageCredentials);
await blob.UploadTextAsync("Blob created by Azure AD authenticated user.");
ViewData["Message"] = "Blob successfully created";
return View();
}
In the Views\Shared\_Layout.cshtml
file
after:
<li><a asp-area="" asp-controller="Home" asp-action="Profile">Profile</a></li>
insert
<li><a asp-area="" asp-controller="Home" asp-action="Tenants">Tenants</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Blob">Blob</a></li>
To access Azure Resource Management (ARM), you'll need a work or school account (AAD account) and an Azure subscription. If your Azure subscription is for a Microsoft personal account, just create a new user in your directory, and use this user to run the sample
OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'AADSTS650052: The app needs access to a service ("https://*.blob.core.windows.net") that your organization "tenantname.onmicrosoft.com" has not subscribed to or enabled. Contact your IT Admin to review the configuration of your service subscriptions. this is because the AzureStorage API was not registered as an API used by your Web App
You can learn more about the tokens by looking at the following articles in MSAL.NET's conceptual documentation:
- The Authorization code flow, which is used, after the user signed-in with Open ID Connect, in order to get a token and cache it for a later use. See TokenAcquisition L 107 for details of this code
- AcquireTokenSilent, which is used by the controller to get an access token for the downstream API. See TokenAcquisition L 168 for details of this code
- Token cache serialization