OpenId Connect Web Sign On with ADFS in Windows Server 2016 TP3
I can’t tell you how excited I am to finally write this post
Yesterday we released the Technical Preview of Windows Server 2016. Yes, it supports containers natively, awesome and groundbreaking, yadda yadda yadda… but if you follow this blog, I know what you look for every time a new Windows Server comes out: if there are news in ADFS, isn’t it?
Boy, does this release deliver on that. ADFS in Windows Server 2016 TP3 comes with brand new support for OpenId Connect web sign on and for OAuth2 confidential clients – moreover, it makes it easy to manage all that through its MMC. No more fiddling with Powershell… unless you are a Powershell wizard, in which case – carry on, good sir/madam.
The ADFS team is going to deliver docs that will cover all the new functionality, but I was too excited to wait – and I suspect you will be in the same boat, too. So I quickly set up a VM, fiddled with the new ADFS app configuration features and adapted some of our Azure AD sample code to do OpenId Connect based sign in against ADFS. That was all surprisingly fast… and easy. The blog below documents that journey. It barely scratches the surface of what’s possible with the new ADFS, but I hope it will show you enough to entice you to try it yourself.
Setting up ADFS in Windows Server 2016 TP3
As you guys know, my administrative skills are nil. Every single time I need something administrative done, I hit Lync – err… Skype for Business I mean – and beg Sam or Dean Wells for their help. But this time I could not wait, hence I decided to try my luck and do it myself. Turns out, it wasn’t that hard.
First thing, I downloaded the ISO of WS2013 TP3 from here. All it took was a quick & painless sign in and registration with my MSA. The file is 4.8 GB.
That done, I set up a new VM in hyper-V. Note, this is all done on my Surface Pro 3 with Windows 10 Pro. I gave the machine 1GB of RAM (although Windows Server only required 512Mb for running, the VM setup will fail with just that. You can reduce it later) and made sure that I had a decent amount of free space on my disk. Turns out that I didn’t delete my old Windows.old folder – between that and other housekeeping I ended up with 40Gb free, which were enough to set things up. The VHDX after all the setups is 10.4Gb, hence very reasonable. As network, I assigned to the VM the “Surface Ethernet Adapter Virtual Switch” – something about the physicality of an ethernet connection makes me feel good. Also remember, when it comes to admin I don’t know what I am doing.
During the VM creation, I specified the downloaded ISO as the setup disk. The booted the VM, and witnessed the usual magic of doing a Windows installation entirely from one SSD – super fast. During the Windows setup I have chosen the “with desktop” variant, which for some mysterious reason is not the default. I guess that working on a no-GUI environment satisfies some self-image requirements for admins… J/K, of course
Anyhow, shortly after I was welcomed by the awesome Windows 10 theme. Ta-dahhh.
If you set up ADFS in the past, you know what’s next. I had to create an AD, which in my thrift setup meant promoting the VM to be a domain controller. That done, I had to configure the ADFS service.
First thing, I went to the server manager/local server tab, scrolled down to roles & features, and under “tasks” I selected “Add Roles and Features”.
Here, I selected AD DS and ADFS – as shown below. Note: IIS is not in the default roles, not I am adding it here. Why? Because ADFS in WS2016 does not need it – it’s all self hosted!
That done, I hit next until Install was the only option – and chose it. After some progress bar fun, I landed on the screen below.
Very helpful! I was able to trigger the next step, “promote this server to a domain controller”, just by clicking on the corresponding link.
I won’t bore you with all the details – also because I did that this morning and I am writing this blog few hours into the evening, hence I simply don’t remember. The main thing I’ll point out is that I chose the “add new forest” given that I want a brand new AD, and that I turned off the DNS service because I don’t need it. Ah, and when you are asked to create a new password – make sure you remember what you choose. It will be required later.
Once you have done all that, the DCpromo will take place… and will take some time, requiring a long reboot. Not as long as it was in the past, I gather, but not instantaneous nonetheless.
Once you come back in, you’ll be reminded in various places that you didn’t set up ADFS yet – just click on any of those and you’ll end up in the configuration wizard. Unfortunately I didn’t take too many screenshots of the process, I was too impatient to get to the apps part but if I figured this out, I am sure you can figure this out too… also remember, this is a super early post. The official documentation will follow soon.
Anyhow. Before starting the ADFS configuration in earnest, there’s one task that one has to do: get a certificate that will be used for transport security and signature purposes. Traditional walkthroughs suggest to get a self signed certificate via IIS, but per the above I didn’t have it installed here and did not want to install it just for spitting out an X509. Luckily, I found a super handy way of doing that via Powershell! It’s all thanks to this article from my good friend Orin. I opened a Powershell prompt and entered the following:
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname WS2016TP3.vibrodomain.net
That command created a selfsigned certificate with a subject of my choosing, of the form <machinename>.<domain>, and saved it on the local machine cert store. The command output provided me with the certificate thumbprint.
Next, I exported the certificate to file – just in case. The next 2 cmdlets did that for me: note that I used the thumbprint for identifying the certificate.
$pwd = ConvertTo-SecureString -String "whatevah" -Force –AsPlainText
Export-PfxCertificate -cert cert:\localMachine\my\1C14CE8E9077970CE27D2ED58154ED6B7F768401 -FilePath c:\WS2016TP3.vibrodomain.net.pfx -Password $pwd
That done, I went back to the ADFS setup wizard. I am sure you’ll find your way through it, but here there are few hints:
- Choose to create a new federation service, as opposed to adding to an existing farm
- You’ll see some certificates dropdown, open them and you’ll see the cert you just created – select it
- As the name of the service, pick something consistent with the subject of the certificate you created.
- When you are asked to choose what account should run the service, choose the administrator. I don’t know if that’s a good idea in prod – remember, this is all for dev time. We’ll do more of those things.
Once you are done, you should land on the screen below. The warnings below do sound a bit scary, but in the end they did not interfere with setting up the application.
We are almost done with the setup! I did just another couple of things.
First, I created one test user in the directory – I don’t like to use the domain admin user directly, given that it has all sorts of odd constraints that are there for excellent reasons in prod but are in the way while developing.
Second, I had to set things up so that I could see ADFS from my host machine, the Surface. To that end:
- I found out the IP assigned to my VM, by opening a prompt and typing good ol’ ipconfig. Say it was 192.168.1.16.
- On my Surface, I launched Notepad as administrator and opened the file c:\Windows\System32\drivers\etc\hosts.
- In that file, I added the line 192.168.1.16 WS2016TP3.vibrodomain.net – associating the name I assigned to ADFS with its IP in my setup. I saved and closed the file.
- I went back to the VM, opened explorer and navigated to where I saved the exported certificate. I copied it in the clipboard and pasted it on the file system of the Surface. I installed it by double clicking on it and working through the import wizard. I elected to choose where to save the cert, picking the local machine and the trusted authorities store.
- Finally, to verify that everything worked – on the Surface I opened an internet browser and navigated to https://ws2016tp3.vibrodomain.net/adfs/.well-known/openid-configuration – OMG OMG OMG is that an OpenId Connect discover doc? – and the prompt “do you want to open or save openid-configuration.json” confirmed that my host can see the ADFS endpoints on the VM just right.
Setting up a Web App for OpenId Connect sign in ADFS
NOW we get to the fun part. Go back to the VM and open the ADFS management console.
There are lots of interesting news you can find if you drill on each of the folders on the left, but here we’ll concentrate on the most obvious new entry – the Application Groups folder. That’s the place where ADFS stores application settings. Select it and choose “Add application group”. You’ll see the wizard below.
The idea is self-explanatory: you are offered a set of application or application topologies templates, covering the gamut of moving parts you encounter in modern authentication. Select some of the entries randomly and you’ll see the wizard steps adapt to the task, growing and shrinking.
Here I am going to the simplest scenario, a web application – hence I pick up the “server application or Website”, fill up name and description and click next.
Looks familiar? : The next steps tells you upfront what clientid is being assigned to your app, and asks you to supply the redirect_uri to use for sending tokens. I plan to use our Azure AD samples or a VS2015 ASP.NET template for actually implement the app, hence I already know what URL to use – it’s the usual https://localhost:44300/ for VS2015 or https://localhost:44320/ for the sample. I can add both, just in case! Once done, I click next.
Here I can select which credentials to assign to my app – big news, given that ADFS didn’t support confidential clients until now. I don’t plan to use any flow requiring creds in this post, but I am adding it anyway for the LOLZ. I chose the “generate a shared secret”, corresponding to the string key we use in almost all Azure AD samples. Note that if you don’t write that down NOW, it’s lost forever and you’ll have to reset it… just like its cloud counterpart.
Also note, super interesting: ADFS can use windows integrated auth as a credential for confidential clients. That makes total sense… and it’s awesome. Just think of the daemon scenarios this enables.
Finally, here there’s the summary. It took what – 40 seconds? Pretty awesome.
And we are done – I mean done done! I can almost hear you. If you have used ADFS in the past, you’re likely to blurt out “wait a minute, what about defining the relying party trust? What about claims mappings”? Have faith, my friends none of that will be necessary.
Setting up an MVP App to Authenticate via OpenId Connect and ADFS
I am finally back on my territory. Setting up an app for talking OpenId Connect to Azure AD or ADFS is, surprise surprise, almost exactly the same operation. There are two quick ways of getting to the app we want. One is to use the VS2015 ASP.NET templates to create an app configured to connect to Azure AD, then modify it to talk to ADFS. The other is to clone one of the OpenId Connect samples for Azure AD, and modify it in the same way (the templates are modeled after the samples). Earlier today I did the templates approach, but for this post I’ll show you how to modify our WebApp-OpenIdConnect-DotNet sample.
Let’s start by cloning the sample. On Windows I like to use the GitHub desktop client, but of course you can use whatever you prefer.
Open the solution in whatever IDE you prefer. The changes are going to be pretty minimal. Here I’ll use VS2013, which just after few weeks of VS2015 already feels retro!
Once the solution is open, compile it – so that all the missing NuGets are restored. That done, head to the web.config and modify the ida: appSettings entries as follows:
<add key="ida:ClientId" value="8219ab4a-df10-4fbd-b95a-8b53c1d8669e" /> <add key="ida:ADFSDiscoveryDoc" value="https://ws2016tp3.vibrodomain.net/adfs/.well-known/openid-configuration" /> <!--<add key="ida:Tenant" value="[Enter tenant name, e.g. contoso.onmicrosoft.com]" /> <add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />--> <add key="ida:PostLogoutRedirectUri" value="https://localhost:44320/" />
In details, the modifications here are:
- the ClientId is the value that you got from ADFS while creating the app. You can always retrieve it by going back to the ADFS console, selecting Application Groups, double clicking on the app group entry and then on the app itself in the apps pane. See below:
- The ida:ADFSDiscoveryDoc is the address of your ADFS discovery document – the issuer metadata in OpenId Connect.
- The tenant and instance entries aren’t used here hence they can be eliminated
That done, we need to tweak the OpenId Connect middleware initialization logic. Head to App_Start/Startup.Auth.cs, and modify the string inits at the beginning of the file as shown below:
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"]; //private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"]; //private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"]; private static string metadataAddress = ConfigurationManager.AppSettings["ida:ADFSDiscoveryDoc"]; private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
No mysteries here, we are just reflecting the changes in the web.config here in code. That done, modify the OpenId Connect middleware options as in the following.
app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = clientId, //Authority = authority, MetadataAddress = metadataAddress, RedirectUri = postLogoutRedirectUri, PostLogoutRedirectUri = postLogoutRedirectUri,
What changed:
- Instead of using the Authority for communicating data about the trusted issuer, we specify the discovery doc location directly via MetadataAddress
- Azure AD does not enforce the rpesence of a redirect_uri in the request, but ADFS does. Hence, we need to add it here
That’s it! Hit F5.
Here there’s our good old sample UX.
Hit Sign in.
Ha! This is exciting! It’s the ADFS default page for forms auth – very similar to the Azure AD one. Here I am signing in as my test user.
And uneventfully, I am signed in! Note the classic domain\username identifier on the top right corner – the logic originally written for Azure AD worked just as well for ADFS.
If you are curious about the content of the default id_token you get from ADFS, you can inspect the incoming ClaimsPrincipal in the index controller (or the immediate window) by adding ClaimsPrincipal cp = ClaimsPrincipal.Current; (which comes from the System.Security.Claims namespace BTW). Screenshot below:
And just like that, you have a functioning MVC app authenticating your local AD users via OpenID Connect. Not bad for few mins’ work, you should probably ask for that raise you’ve been thinking about!
Summary
Ignore the DCPromo and ADFS setup, which are going to be done by your admins (anyway, that were so straightforward that even I managed to do it in very little time and without help). The new ADFS in the Windows Server 2016 TP3 makes it very easy to provision applications, and its support for modern app topologies is finally comprehensive. The OWIN middleware in Katana / ASP.NET, already well proven in Azure AD scenarios, works as is with ADFS –and the delta between the code required in the two cases is risible.
I am loving it.. and that’s only the beginning. Expect upcoming official docs to describe all the other options in depth – I am sure you will be as delighted and excited as I am right now. Huge kudos to the ADFS team for an awesome prerelease – I am sure they are looking forward for your feedback.
Go ahead, download Windows Server 2016 TP3, set up ADFS and… happy coding!