alvinashcraft
shared this story
from Nick's .NET Travels.
In this post we’re going to look at how to use Azure Active Directory to secure a web api built using ASP.NET (full framework – we’ll come back to .NET Core in a future post). To get started I’m going to create a very vanilla web project using Visual Studio 2017. At this point VS2017 is still in RC and so you’ll get slightly different behaviour than what you’ll get using the Visual Studio 2015 templates. In actual fact the VS2015 templates seem to provide more in the way of out of the box support for OWIN. I ran into issues recently when I hadn’t realised what VS2015 was adding for me behind the scenes, so in this post I’ll endeavour not to assume anything or skip any steps along the way.
After creating the project, the first thing I always to is to run it and make sure the project has been correctly created from the template. In the case of a web application, I also take note of the startup url, in this case http://localhost:39063/. However, at this point I also realised that I should do the rest of this post following some semblance of best practice and do everything over SSL. Luckily, recent enhancements to IIS Express makes it simple to configure and support SSL with minimal fuss. In fact, all you need to do is select the web project node and press F4 (note, going to Properties in the shortcut menu brings up the main project properties pane, which is not what you’re after) to bring up the Properties window. At the bottom of the list of properties is the SSL Enabled and SSL URL, which is https://localhost:44331/. Take note of this url as we’ll need it in a minute.
To setup the Web API in order to authorize requests, I’m going to create a new application registration in Azure Active Directory. This time I need to select Web app / API from the Application Type dropdown. I’ll give it a Name (that will be shown in the Azure portal and when signing into use this resource) and I’ll enter the SSL address as the Sign-on URL. This URL will also be listed as one of the redirect URIs used during the sign in process. During debugging you can opt to do this over HTTP but I would discourage this as it’s no longer required.
After creating the application, take note of the Application Id of the newly created application. This is often referred to as the client id and will be used when authenticating a user for access to the web api.
Application Id (aka Client Id): a07aa09e-21b9-4e86-b269-a18903b5fe54
We’re done for the moment with Azure Active Directory, let’s turn to the web application we recently created. The authorization process for in-bound requests involves extracting the Authorization header and processing the bearer token to determine if the calling party should have access to the services. In order to do this for tokens issues by Azure AD I’ll add references to both the Microsoft.Own.Security.ActiveDirectory and Microsoft.Own.Host.SystemWeb packages.
Note: Adding these references takes a while! Make sure they’re completely finished before attempting to continue.
Depending on the project template, you may, or may not, already have a Startup.cs file in your project. If you don’t, add a new item based on the OWIN Startup class template
The code for this class should be kept relatively simple:
[assembly: OwinStartup(typeof(SampleWebApp.Startup))]
namespace SampleWebApp
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
Additionally, you’ll want to add another partial class file Startup.Auth.cs in the App_Start folder.
namespace SampleWebApp
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
}
}
}
And now we get to adding the middleware that will be used to process the authorization header
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
});
}
This uses the configuration manager to extract the Tenant and Audience settings from the web.config (and subsequently the Azure portal settings when you publish to the cloud):
<add key="ida:Audience" value="a07aa09e-21b9-4e86-b269-a18903b5fe54" />
<add key="ida:Tenant" value="nicksdemodir.onmicrosoft.com" />
The tenant is the Id, or in this case, the domain of the tenant where the application is registered. The Audience is the application Id of the application registered in Azure AD.
Warning: If you run the application now and get an error relating to a missing type, you may have to revert the Microsoft.Owin.Security.ActiveDirectory to the most recent v4 package. At the time of writing this post there seems to be an incompatibility between v5 and Owin.
Reference to type 'TokenValidationParameters' claims it is defined in 'System.IdentityModel.Tokens.Jwt', but it could not be found
Ok, we’re ready to try making requests. I’m going to use Fiddler but you can use any other tool that’s able to generate and send HTTP requests. The first attempt will be a GET request to https://localhost:44331/api/values which is one of the default controllers that was created from the project template. Depending on what your project template included, the valuescontroller may, or may not, have the Authorize attribute applied to it. If, like me, you didn’t have the Authorize attribute applied to the valuecontroller, you should get a valid response back to your HTTP request. In this case, you’re going to want to add security to the valuecontroller by adding the Authorize attributes:
[Authorize]
public class ValuesController : ApiController
{
Now, try making the request again – you should now get a 401 Unauthorized error. The body of the response should say:
{"Message":"Authorization has been denied for this request."}
Clearly this is the case since we didn’t send the Authorization header. This time, let’s add an Authorization header, with the word “Bearer” and a token consisting of random text:
Authorization: Bearer abcdefg
This should generate the same response. However, let’s start to look into this further. To get more diagnostic information, add the following to the web.config file for the project
<system.diagnostics>
<switches>
<add name="Microsoft.Owin" value="Verbose" />
</switches>
</system.diagnostics>
Now when you make the request you should see more diagnostic information in the Output window in Visual Studio:
Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware Error: 0 : Authentication failed
System.ArgumentException: IDX10708: 'System.IdentityModel.Tokens.JwtSecurityTokenHandler' cannot read this string: 'abcdefg’.
The string needs to be in compact JSON format, which is of the form: '<Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>'.
As we should have predicted, the token we passed in isn’t a value Jwt – it’s not even valid JSON. Let’s fix this by generating an actual access token for this Web API. In a previous post I walked through manually the process of authenticating, retrieving an authorization code and then exchanging it for an access token. I’ll do the same here.
First I’m going to launch an authorization url in the browser and sign in using credentials from the nicksdemodir.onmicrosoft.com tenant:
https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/oauth2/authorize?client_id=a07aa09e-21b9-4e86-b269-a18903b5fe54&response_type=code&redirect_uri=https://localhost:44331/
The authorization url is made up of various components:
nicksdemodir.onmicrosoft.com – This is the domain name of the tenant where the web application is registered with Azure AD. You can also use the tenant Id (guid format)
a07aa09e-21b9-4e86-b269-a18903b5fe54 – This is the application id of the application registration in Azure AD
code – This indicates that the response should be an authorization code
https://localhost:44331/ - This is the uri that the browser will be redirected back to, passing with it the code in the query string.
Make sure you have the web application running, otherwise the redirect uri won’t resolve at it may be hard to extract the code from the query string (depending on the browser). After signing in, you’ll be redirected back to your web application with a URL similar to (the code has been shortened for brevity):
https://localhost:44331/?code=zvrs_zz0…….05B_ggAA&session_state=ef2986b8-75bd-484a-b9b9-68f0e46ab569
The next thing to do is to prepare a POST request in your http tool of choice with the following:
URL: https://login.microsoftonline.com/nicksdemodir.onmicrosoft.com/oauth2/token
Body: grant_type=authorization_code&client_id=a07aa09e-21b9-4e86-b269-a18903b5fe54&client_secret=c06kP0Q9ENGpZGbiZTqB1QQaZUWNe190mCittRMr&redirect_uri=https://localhost:44331/&code=zvrs_zz0…….05B_ggAA&resource=a07aa09e-21b9-4e86-b269-a18903b5fe54
The Body parameters are broken down as:
a07aa09e-21b9-4e86-b269-a18903b5fe54 – This is the application id of the application registration in Azure AD. It’s required as both the client_id and the resource, since we’re using the access token to access the web application itself.
c06kP0Q9ENGpZGbiZTqB1QQaZUWNe190mCittRMr – This is a private key (aka client secret) issued by the Azure AD application to ensure the security of token requests. I’ll come back to this in a second and show how to create one for your application.
https://localhost:44331/ – The redirect uri for the application – required here to verify the calling party as it has to align with what’s in Azure AD
zvrs_zz0…….05B_ggAA – This is the authorization code returned in the previous step
To generate the client secret in Azure AD simply click on the Keys tab within the details of the application registration. You can then create a new key by entering a description. The description is only seen by you, so give it a name that’s meaningful to you. Note that once you save the new key, you will only be shown the value of the key once. Once you leave the page, the value of the key will never been shown again.
The key created in the Azure AD should be used as the client secret when doing the authorization code to access token exchange.
The response to this POST should return JSON which includes and access token value. Add the access token to the authorization header:
Authorization: Bearer G1ZsPGjPF6qJO8Sd5HctnKqNk_8KDc-………Lpy9P8sDWdECziihaPWyseug9hgD119keoZuh4B
This should give you a 200 response with data. And there you have it – you’ve successfully secured your web api so that it requires the user to be authenticated using Azure Active Directory.