Securing a Web API with ADFS on WS2012 R2 Got Even Easier
Few weeks ago I gave you a taste of how you can use the modern ASP.NET OWIN stack for securing a Web API with tokens obtained from the latest ADFS version, the one in Windows Server 2012 R2. The flow I described was definitely easier than the one you’d have to implement should you choose to use the JWT handler directly, but it still required quite a lot of code.
Well, good news! With the RTM of VS2013, we now have few new toys we can play with to make things easier. And that’s what I am going to do here.
Walkthrough
To make things more interesting, I am going to use the same sections structure I have used for the old tutorial but I’ll do few things differently, so that if you want to mix & match features you can take one section from here, and another from there.
The scenario I want to implement is practically the same, to the point that I can paste the old description (almost) verbatim:
The scenario we want to implement is pretty simple: we want to restrict access to an MVC5 Web API to the users of a given on-premises AD instance, which happens to be using Windows Server 2012 R2 ADFS (just “ADFS” from now on). Furthermore, we want to expose the Web API to the user via a .NET application.
I have highlighted the differences in red:
- This time I will take advantage of the new features in VS2013 to make things quicker, hence we’ll be playing with the latest Web API; but yo can obtain the same result if you build the project “by hand” if you still w ant to target VS2012
- Between the time I wrote the old tutorial and today lots of things happened – and one of the most relevant for this is that ADAL .NET hit GA! Hence, here’ I’ll use ADAL .NET for building the test client. Note that if you want to stick with Windows Store you can follow the same instructions as the old tutorial, the resulting client will work just fine with the Web API described here.
Setting Up the Web API Project
As I described here, since RC VS2013 supports the creation of Web API projects secured via Windows Azure AD organizational accounts.
Now, that’s not *exactly* what we want to do here: here we want to secure the Web API with Windows Server AD. The templates do not currently support our target scenario directly as of today; that said, the project they generate comes pretty darn close to what we need, hence the fastest way is to create a Windows Azure AD-secured Web API project and tweak its code afterwards. Go ahead and create such a project, you can use this as a rough reference. I have only one small suggestion: when you go through the authentication settings wizard, expand “more options” and change the default “App ID URI” (which is based on the windows azure AD tenant, which you will not use beyond this point) into something that will make sense for your service on-premises. Something like the following:
Done? Excellent! Now you have a project ready to tweaked. Also, you have an application entry in your directory that you’ll never use: next time you swing by the Windows Azure portal, remember to delete it to tidy up
Alright, tweaking time! Go under App_Start and open Startup.Auth.cs. You’ll find the following code:
public partial class Startup { public void ConfigureAuth(IAppBuilder app) { app.UseWindowsAzureActiveDirectoryBearerAuthentication( new WindowsAzureActiveDirectoryBearerAuthenticationOptions { Audience = ConfigurationManager.AppSettings["ida:Audience"], Tenant = ConfigurationManager.AppSettings["ida:Tenant"] }); } }
Looks familiar? Of course it does. It is the code which inject OWIN middleware with the longest name on the planet, which also happens to secure
calls by validating incoming tokens as coming from the indicated Windows Azure AD tenant.
During last tutorial I created a custom class for overriding the default behavior of that middleware, to source the validation keys form the ADFS metadata instead of from the Windows Azure AD tenant metadata. However, I also mentioned that such trick would soon be no longer necessary.
Well, guess what: it is no longer necessary – the inimitable Barry added a specialized middleware for ADFS, which looks just as simple as the Windows Azure one. Comment out the call to UseWindowsAzureActiveDirectoryBearerAuthentication and substitute it with the following:
app.UseActiveDirectoryFederationServicesBearerAuthentication( new ActiveDirectoryFederationServicesBearerAuthenticationOptions { Audience = ConfigurationManager.AppSettings["ida:Audience"], MetadataEndpoint = ConfigurationManager.AppSettings["ida:MetadataEndpoint"] });
Super-straightforward! All you need to do next is to go in the AppSettings in web.config and add the following entry for your ADFS’s metadata doc address:
<add key="ida:MetadataEndpoint" value="https://sts.contoso100.com/federationmetadata/2007-06/federationmetadata.xml" />
That is all. Quite the improvement keep the shell open, we’ll need it gain soon.
Setting up the Web API in ADFS
In the old tutorial I gave you detailed step by step instructions on how to use the ADFS management UX to register the web API as a relying party trust. Given that it was quite a lot of screenshots, I didn’t feel like doing it all over again: hence I asked my friend Jairo Cadena, PM on the ADFS team, for a little help in coming up with the most compact PowerShell script he could come out with for the purpose. The results are pretty awesome, all you need is a single well-crafted line!
Log in your server on which ADFS is running, and launch PowerShell from the server manager; then, modify the following line to use the coordinates of your web API and paste it right at the prompt:
Add-ADFSRelyingPartyTrust -Name MyWebAPI -Identifier http://myadfsvagaries/webapi -IssuanceAuthorizationRules ‘=> issue(Type = “http://schemas.microsoft.com/authorization/claims/permit”, Value = “true”);’ -IssuanceTransformRules ‘c:[Type == “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress”] => issue(claim = c);’
Hit enter and – magic – your API is now provisioned as a known RP in ADFS.
Setting Up a Test Client Project in ADFS and in Visual Studio
We’re almost done: here I am going to follow a simplified flow which will make things much faster.
Registering a client in ADFS
In the old tutorial I wanted to show you how to leverage the WebAuthenticationBroker’s SSO mode, which requires you to use as Redirect URI the identifier of the Windows Store app, which in turn needs you to do some work to find out its value. However here I am using ADAL .NET, which does not really have a SSO mode (there’s no need for it on the desktop, given that there are no sandboxing rules to relax with special modes). Also, .NET apps don’t have such identifiers. Hence, pretty much any URI will do! Given that you still have the PowerShell open, go ahead and paste something to the effect of the following:
Add-ADFSClient -Name “MyClient” -ClientId “E1CF1107-FF90-4228-93BF-26052DD2C714” -RedirectUri “http://anarbitraryreturnuri/”
Done. ADFS has an entry for the client too.
Create the Client Project
Here any rich client project type will do: WPF, WInForm, even a console app (though in that case make sure you place [STAThread] where you’ll run ADAL). I am building a WPF app here, for old times’ sake
Add a reference to the ADAL NuGet. Make sure you don’t pick the beta! (I know, I know… we’ll tidy up soon).
Add a button and double click on it to generate an event handler. Here, add the following:
private async void Button_Click(object sender, RoutedEventArgs e) { string authority = "https://sts.contoso100.com/adfs"; string resourceURI = "http://myadfsvagaries/webapi"; string clientID = "E1CF1107-FF90-4228-93BF-26052DD2C714"; string clientReturnURI = "http://anarbitraryreturnuri/"; AuthenticationContext ac = new AuthenticationContext(authority, false); AuthenticationResult ar = ac.AcquireToken(resourceURI, clientID, new Uri(clientReturnURI)); string authHeader = ar.CreateAuthorizationHeader(); HttpClient client = new HttpClient(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44302/api/Values"); request.Headers.TryAddWithoutValidation("Authorization", authHeader); HttpResponseMessage response = await client.SendAsync(request); string responseString = await response.Content.ReadAsStringAsync(); MessageBox.Show(responseString); }
By now you know that the actual ADAL code is only in the three highlighted lines above, to 1) initialize the authority to point to ADFS 2) obtain a token for the resource and 3) put it in form of header – everything else is there for legibility and for the REST call to the API.
Don’t forget to add usings for Microsoft.IdentityModel.Clients.ActiveDirectory and System.Net.Http. You need to add a reference to System.Net.Http BTW. Also, note the async keyword in the handler declaration.
Testing the Solution
Let’s test the solution. Start the Web API in debug mode.
Small aside: if you have a spank-new system (as it’s the case for me Surface Pro 2, baby!) chances are that when starting the web API you’ll get the following:
That’s because the Web API is creating the SSL channel using the development certificate from IIS Express, which is of course untrusted. For web based apps that can stay that way, given that you always have the option of hitting “continue”. However for Web API scenarios that’s more problematic, given that clients will refuse to establish an SSL channel with an untrusted SSL certificate. Here you have 2 options: install the certificate among the trusted roots of the development machine (easy to do: hit continue, click “certificate error” on the IE bar, click view certificates, click install certificate, choose local machine/trusted roots certification authorities) or disable the channel check in the client application. Personally I much prefer the former, given that with the latter you risk forgetting to fix the code before deploying to production.
Once the API is running, start the client as well.
Hit the button. You’ll be prompted by the usual ADAL dialog, pointed to our ADFS test instance. Sign in.
Once you enter your credentials, voila’!
The dialog demonstrates that the Web API was successfully called, concluding our little walkthrough. If you want to make sure the check is really taking place, try to mess with the auth string before sending it and observe the results
Wrap
Wow. I wrote the first tutorial on how to use ADFS for securing Web API at the end of July: in less than 3 months, things got dramatically simpler – just compare the length of the old tutorial (which in itself depended on yet another one) with the length of this!
Not only things got simpler, the products involved (Windows Server 2012 R2 and ADAL .NET) are now generally available for your to use in production. If you are working on a solution using those technologies, we want to hear form you! Hit me on my contact page and I’ll be happy to route you accordingly