Its just a few weeks ago that I started looking in more detail at the Windows Azure AppFabric Access Control Service (or just ACS to its friends) and one of the first things I wanted to figure out was how to federate between my on-premise Active Directory domain and ACS. With a few pointers, its not too tricky, but I figured I’d walk you through the whole process anyhow!
I’m going to assume that you’ve got an account set up with AppFabric Labs (http://portal.appfabric.labs.com) and an Active Directory domain installed too – if you don’t (or you want play about on a test domain) then just spin up a VM running Windows Server 2008 (or R2) and Install Active Directory Domain Services.
Here’s what we’ll do:
Download and Install ADFS v2
Create a Self-Signed SSL Certificate
Before you can configure ADFS you’ll need an SSL Certificate configured on your machine – if you have one already, skip ahead to the next step. Otherwise take you can setup a self-signed (untrusted) certificate using IIS. It goes without saying that you shouldn’t use a self-signed certificate in a production environment!
Configure ADFS and Enable Windows Authentication Endpoints
Establish a Trust relationship from ADFS to ACS
So, ADFS is now acting as an STS (Security Token Service) for your Active Directory domain, this means that you’re ready to setup federated authentication with other STSs or Identity Providers like the Windows Azure AppFabric Access Control Service (ACS). To do that, we need to establish a bidirectional trust relationship between ADFS and ACS – this allows your ACS project to process incoming tokens from your Active Directory and ensure that Active Directory trusts ACS and will issue tokens for use with it. We’ll be using AppFabric Labs for this – its free, so go Sign Up for an account if you don’t have one already.
Next, we need to perform a very similar process on your ADFS server, establishing a trust relationship back to ACS.
Establish a Trust relationship from ACS to ADFS
ADFS won’t issue tokens for use against the Access Control Service if it doesn’t trust it – we’ll need to configure a default signing certificate in ACS, generate some Federation Metadata and share that with our ADFS instance to finish off the establishment of this trust relationship.
When you’re done, leave the ‘Open the Edit Claim Rules dialog’ option checked – we’ll use this dialog to add some rules to this trust relationship allowing data to flow from Active Directory and into ACS. To get back to this dialog, right click on the Relying Party Trust for ACS and click ‘Edit Claim Rules’
At this point, some congratulations are in order: you’ve installed and configured ADFS, you’ve created and uploaded a certificate to allow signing of your ACS Federation Metadata, you’ve established a bi-directional trust relationship between ADFS and ACS and you’ve specified some claims which can be passed through to the Access Control Service. There’s just a little more work ahead of you before you can perform that first piece of federated, claims-based authentication.
We’re almost done. Now that we’ve established a claims processing rule (pass through the Windows Account Name) associated with our newly trusted identity provider (your Active Directory Federation Server) we can fire up Visual Studio and write some code. In this next section, we’ll get a SAML Token from ADFS and exchange it for a SWT token via the access control service. Once you’ve got the Simple Web Token (SWT) the Authentication World becomes Your Oyster – go and use it to auth against web services/sites or anything else!
Use the Windows Identity Foundation to get a SAML token from ADFS
There are a couple of things we need to do to get a token from ADFS. Firstly, we’ll need to validate that Self-Signed Certificate we created a while ago for securing SSL traffic to and from the ADFS server, secondly we’ll use the Windows Identity Foundation to request the token from an ADFS endpoint.
private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors){ return sslPolicyErrors == SslPolicyErrors.None || string.Equals(certificate.Issuer, "CN=vm-willpe-adfs.willpe.sb", StringComparison.InvariantCultureIgnoreCase);}
ServicePointManager.ServerCertificateValidationCallback += RemoteCertificateValidationCallback;
var stsUrl = "https://vm-willpe-adfs/adfs/services/trust/13/windowsmixed";WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential), new EndpointAddress(new Uri(stsUrl)));trustChannelFactory.TrustVersion = TrustVersion.WSTrust13; trustChannelFactory.Credentials.Windows.ClientCredential.Domain = "<<Your NetBios Domain Name Here >>;trustChannelFactory.Credentials.Windows.ClientCredential.UserName = @"<<Your Domain Username Here>>";trustChannelFactory.Credentials.Windows.ClientCredential.Password = @"<<Your Domain Password Here>>";
var acsUrl = "https://willpe-blog.accesscontrol.appfabriclabs.com/";RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer);rst.AppliesTo = new EndpointAddress(acsUrl);rst.TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.Saml2TokenProfile11; WSTrustChannel channel = (WSTrustChannel)trustChannelFactory.CreateChannel();GenericXmlSecurityToken token = channel.Issue(rst) as GenericXmlSecurityToken;string tokenString = token.TokenXml.OuterXml;return tokenString;
private static string GetSamlToken(string domain, string userName, string password){ var acsUrl = "https://willpe-blog.accesscontrol.appfabriclabs.com/"; var stsUrl = "https://vm-willpe-adfs/adfs/services/trust/13/windowsmixed"; WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential), new EndpointAddress(new Uri(stsUrl))); trustChannelFactory.TrustVersion = TrustVersion.WSTrust13; trustChannelFactory.Credentials.Windows.ClientCredential.Domain = domain; trustChannelFactory.Credentials.Windows.ClientCredential.UserName = userName; trustChannelFactory.Credentials.Windows.ClientCredential.Password = password; try { RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer); rst.AppliesTo = new EndpointAddress(acsUrl); rst.TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.Saml2TokenProfile11; WSTrustChannel channel = (WSTrustChannel)trustChannelFactory.CreateChannel(); GenericXmlSecurityToken token = channel.Issue(rst) as GenericXmlSecurityToken; string tokenString = token.TokenXml.OuterXml; return tokenString; } finally { trustChannelFactory.Close(); }}
Add a line to the Main method to call GetSamlToken and verify that you get a nice, chunky XML token back from ADFS. With just a couple more lines of code, all of that markup will become a Simple Web Token.
Submit the SAML token to ACS to retrieve a set of claims as a SWT token
The hard work is over now – all we need to do is submit the SAML token we retrieved to ACS and get a Simple Web Token in response. With that token, you’re good to authenticate against any (properly configured) resource secured by the AppFabric Access Control Service.
NameValueCollection parameters = new NameValueCollection();parameters.Add("wrap_scope", "https://willpe-blog.accesscontrol.appfabriclabs.com/mgmt");parameters.Add("wrap_assertion_format", "SAML");parameters.Add("wrap_assertion", samlToken);
private static string GetSwtToken(string samlToken){ var appliesTo = "https://willpe-blog.accesscontrol.appfabriclabs.com/mgmt"; var acsStsUrl = "https://willpe.accesscontrol.appfabriclabs.com/WRAPv0.9/"; try { WebClient client = new WebClient(); client.BaseAddress = acsStsUrl; NameValueCollection parameters = new NameValueCollection(); parameters.Add("wrap_scope", appliesTo); parameters.Add("wrap_assertion_format", "SAML"); parameters.Add("wrap_assertion", samlToken); byte[] responseBytes = client.UploadValues("", parameters); string response = Encoding.UTF8.GetString(responseBytes); return Uri.UnescapeDataString(response .Split('&') .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase)) .Split('=')[1]); } catch (WebException wex) { string value = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd(); throw; }}
So, there we go – we’ve installed and configured ADFS, Created a Trust Relationship between the AppFabric Access Control Service and your new ADFS Instance, we configured ACS to accept incoming claims from the Active Directory Identity Provider and pass-through the Windows Account name. Then we used WIF to get swap our Windows Credentials for a SAML token which we sent to ACS to get a Simple Web Token. The result? A little string like this:
http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname=WILLPE\Test &http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider=http://VM-WILLPE-ADFS.willpe.sb/adfs/services/trust &Audience=https://willpe-blog.accesscontrol.appfabriclabs.com/mgmt &ExpiresOn=1288060895 &Issuer=https://willpe-blog.accesscontrol.appfabriclabs.com/ &HMACSHA256=[Base64-Hash-Code]
You can download the full sample from SkyDrive – feel free to leave any comments/questions below or find me on Twitter (@willpe)
I hope this helps,
Will