Protecting a .NET Core API with Azure Active Directory

Andreas Helland
Contosio Labs
Published in
8 min readJul 5, 2016

--

And access it from a client using MSAL and the AAD v2 endpoint just for kicks :)

Update 19th October 2017: Time has passed, and a new version of .NET Core is out. 2.0 brings some changes to the authentication pipeline, and I have covered a few of these in a new article: https://contos.io/working-with-identity-in-net-core-2-0-d235a7bf9cbe

Azure Active Directory has been fairly stable for quite some time now with regards to frameworks and libraries, but there are some upcoming changes that might pose a challenge should you want to tackle them all at once.

.NET Core 1.0 went GA last week — since this introduces slight differences in setting up a Web App/API it also changes how you enable authentication server side.

There is also a new and converged endpoint for authentication, (known as the v2 endpoint), supporting both Azure AD + MSA accounts which requires the client to do the token acquisition dance slightly different. (MSA accounts are former “Live” accounts, now known as a Microsoft account.)

And the old trusty Active Directory Authentication Library (ADAL) is being replaced by Microsoft Authentication Library (MSAL) to take advantage of this converged endpoint.

Do you need to take care of all this at once? No, by all means. The full .NET framework isn’t going out of style yet, and both the v1 AAD endpoint and ADAL will be supported going forward as well. And if you want/need to change the story parts at a time it will be possible to do a migration in steps.

I want to see if I can go all in :)

Hold on to the railing so you don’t lose your footing

I have a sample client and server setup that works with the “old” bits, that we will see if we can turn into “new” bits.

A thorough walkthrough of the existing code can be found here:

The setup is fairly stripped down. There is a Web API protected by Azure AD, and there is a Windows Universal app calling into the API by acquiring a token first, and then performing a GET action. It uses ADAL and the v1 endpoint to do this.

To make things a bit clearer I’ll build the code anew rather than edit the existing in a number of places, but the end result will be the same.

Core Web API Server

To start off the server part the easy way is by going with the “ASP .NET Core Web Application (.NET Core)” template in Visual Studio and hook up AAD. This will stub out an MVC patterned Web API, and integration working with the v1 endpoint of AAD. This is a working API that is ready to go, so it is a good starting point even though we will be changing the essential parts.

Visual Studio “Add New Project”
Adding Azure AD authentication on new project

When the IDE is ready you can delete the following files:
Project_Readme.html
ValuesController.cs

Then you need to add a new Web API Controller:
AADController.cs

This should contain the following code:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace CoreWebAPIServer.Controllers
{
[Authorize]
[Route(“api/[controller]”)]
public class AADController : Controller
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { “World” };
}
}
}

The “[Authorize]” attribute requires an extra using statement compared to .NET 4.x, but otherwise it’s the same as before.

Since the wizard only registered in AAD v1 we need to configure an entry behind the v2 endpoint before reconfiguring the authentication settings. The registration process is described here:
https://aadguide.azurewebsites.net/integration/registerappv2/

Should you want to skip the details you can head directly to the portal and play around:
https://apps.dev.microsoft.com/

What you want is a Web App, and the value you need for your settings is the App ID / Client Id.
You will also need to specify the redirect URI which in my case happens to be https://localhost:44399/.
(Right-click project and select “Properties”.)

Properties for project

And add “signin-oidc” so it looks like this:

Adding “Web platform” to Azure AD v2 endpoint portal

While you’re at it add a “Mobile application” as well which we’ll need to have in place for our client app afterwards. (Earlier when you had a setup with an API and a client you would set up separate app entries for them in Azure AD, but that is not needed any longer.)

Now you might be thinking what a redirect URI is needed for in an API. Well, it isn’t needed here, but if you add an application secret you could use the id for interactive logins on a web page too so it’s just to simplify your options when creating an app.

Open up your appsettings.json file to rewire the Azure AD authentication parameters. You need to replace “ClientId” with the new one you generated. If you registered a different name for the v2 endpoint than the name the of the project change “Audience” accordingly. Your file should look similar to this:

{
“Authentication”: {
“AzureAd”: {
“AADInstance”: “https://login.microsoftonline.com/",
“Audience”: “https://contoso.onmicrosoft.com/CoreWebAPIServer",
“ClientId”: “client-guid”,
“Domain”: “contoso.onmicrosoft.com”,
“TenantId”: “tenant-guid”
}
},
“Logging”: {
“IncludeScopes”: false,
“LogLevel”: {
“Default”: “Debug”,
“System”: “Information”,
“Microsoft”: “Information”
}
}
}

Almost there now. It’s only the crucial part that might break everything that is left :) When the API receives a token from the client it needs to validate that this is an acceptable token.
The parameters for the validation of the incoming tokens are in Startup.cs.

Replace this piece:

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = Configuration[“Authentication:AzureAd:AADInstance”]
+ Configuration[“Authentication:AzureAd:TenantId”],
Audience = Configuration[“Authentication:AzureAd:Audience”]
});

With this snippet:

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = Configuration[“Authentication:AzureAd:AADInstance”]
+ Configuration[“Authentication:AzureAd:TenantId”],
Audience = Configuration[“Authentication:AzureAD:ClientId”],
TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidIssuer =
Configuration [“Authentication:AzureAd:AADInstance”]
+ Configuration[“Authentication:AzureAd:TenantId”] + “/v2.0” }
});

You will notice a couple of things have changed here.
I changed the “Audience” to be based on the ClientId, and I have added a step specifically for validating that the token was issued by the v2 endpoint. Since this relies on the TenantId this also means that even though the endpoint is converged, and accepts multiple types of credentials, we have narrowed down the scope to only accept logins from one specific tenant effectively making it a single tenant app. (This may or may not be what you want, so be aware of this before troubleshooting exotic error messages.)

UWP Web API Client

That takes care of the server part of the equation. Let’s see if we can create a corresponding client that will work as well.

Create a new project using the “Blank App (Universal Windows)” template:

Visual Studio “Add New Project”
Select Target and Minimum versions for project

Add a StackPanel to the grid in MainPage.xaml:

<StackPanel>
<Button Name=”HelloWebApi”
Content=”Hello Web API!”
VerticalAlignment=”Top”
Margin=”10"
Click=”HelloWebApi_Click”/>
<TextBlock Name=”lblHelloAPIOutput”
Text=”Click button to authenticate through Azure AD,
and retrieve the output from a predefined web api.”
Margin=”10"
TextWrapping=”Wrap”/>
</StackPanel>

Before implementing the Event Handler you should include the Microsoft Authentication Library through NuGet:

Microsoft.Identity.Client a.k.a. Microsoft Authentication Library

The click event is making a simple HttpClient call to the API:

private async void HelloWebApi_Click
(object sender, RoutedEventArgs e)
{
var authResult = await GetToken();
string token = authResult?.Token;
if (token == null)
{
MessageDialog dialog = new MessageDialog(“If the error
continues, please contact your administrator.”,
“Sorry, an error occurred while signing you in.”);
await dialog.ShowAsync();
}
if (token != null)
{
HttpClient client = new HttpClient();
string apiRequest = $”https://{baseApiUrl}/api/AAD”;
client.DefaultRequestHeaders.Authorization =
new Windows.Web.Http.Headers.HttpCredentialsHeaderValue
(“Bearer”, token);
var response = await client.GetAsync(new Uri(apiRequest));
string content = await response.Content.ReadAsStringAsync();
lblHelloAPIOutput.Text = content.ToString();
}
}

The interesting part in MainPage.xaml.cs is the GetToken method which unsurprisingly attempts to acquire a token:

private static async Task<AuthenticationResult> GetToken()
{
var clientApp = new PublicClientApplication(clientId);
string[] scopes = { clientId }; AuthenticationResult result = null;
try
{
//If you want to go straight to the branded login page
//result = await clientApp.AcquireTokenAsync
//(scopes,””, UiOptions.SelectAccount,
//string.Empty,null,authority,null);
//var authority = $”{aadInstance}{tenantId}”;
//To go to the generic login page
//(which will dynamically change/redirect)
result = await clientApp.AcquireTokenAsync(scopes, “”,
UiOptions.SelectAccount, string.Empty);
}
catch (Exception x)
{
if (x.Message == “User canceled authentication”)
{
// Do nothing
}
return null;
}
return result;
}

You’ll notice that there’s two options here depending on whether you want to go straight to the branded login page of the tenant, or if you want to go through a generic page first.
For a single tenant it makes sense to hardwire the login, whereas if you want to have a multi-tenant app you might want to go the other route.

An option I haven’t shown here is implementing “hints” which allow you to direct to a specific tenant based on knowing something about the user before they type in their credentials.

The token acquisition as shown here is perfect for a debugging scenario — you will always be prompted to authenticate, and no cache will be bothering you.

For an app released to actual users you might want to attempt a call to clientApp.AcquireTokenSilentAsync first to see if you can authenticate without even asking the user. UiOptions have different switches as well; ActAsCurrentUser, ForceConsent, ForceLogin and SelectAccount, also reflecting your specific use case.

This means that for someone using Windows 10 and logging in with their Azure AD credentials the experience could be entirely seamless. But if you know that the Windows credentials are not the same identity as you want in the app, you should provide an option for the user to configure things to work with their setup.

A second point to note is that the scope in the client is the same as the audience in the server configuration.

When you run the app, and successfully authenticate you should see the result of the API call like this:

CoreWebAPIClient — ignore re-use of screenshot from “old” app :)

This isn’t a “review” of the old vs new approach for implementing authentication based on Azure AD, so I can’t really say one way is better than the other. Depending on your starting point you might feel it makes more sense with .NET Core, or you might find it annoying — eye of the beholder :)

If you want the full code samples they are on GitHub in the CoreWebAPIServer and CoreWebAPIClient projects:
https://github.com/ahelland/AADGuide-CodeSamples

--

--