Exercise 2: Using ACS with SAML Tokens

In the first exercise we have shown how you can use ACS for enabling claims-based access without requiring anything more than the classic tools you’d use in REST API integration scenarios. In this exercise we will explore a more advanced case.

Let’s say you want to make your REST service available to the users stored in a business repository such as Active Directory: this is a common requirement, but one that traditional approaches to REST security fail to address. ACS solves this situation: if that repository has an ADFSv2 instance associated to it, ACS can easily leverage it for enabling AD accounts to securely invoke REST services.

The idea behind the scenario is very simple. The ADFSv2 instance can be a trusted issuer for your ACS Service Namespace, much like the issuer we defined in task 4 of the former exercise: the only difference is that it will issue tokens in SAML format instead of SWT. A client will request ACS tokens by sending one of such SAML tokens: the claim content of the SAML token is what the ACS will use for determining the content of the token it will issue to the requestor. From now on, it will be business as usual: the service will receive an SWT token, precisely as in shown in the first exercise, and will use it for determining access.

In this exercise you will learn how to modify a client in order to obtain a SAML token from an ADFSv2 instance and use it for requesting a token from ACS. You will also see how you can leverage the ADFSv2 metadata documents for automating part of the settings that the management service needs for configuring access. Finally, you will see how claims allow you to leverage user attributes such as group memberships for creating sophisticated access logic to your REST web service.

Figure 1 A summary of the scenario enabled by this exercise. The client requests (1) and obtains (2) a SAML token from the ADFSv2 instance; it uses the same SAML token for requesting a token from ACS (3) and obtains a SWT token in return (4). The client then uses the SWT to invoke the service (5) and, upon successful authorization in ACSAuthorizationManager Devil, reaches the intended service method.

Task 1 - Configure the ACS service to accept users from a directory

In this task we are going to build up on the ACS service we have built in exercise 1.

We already have a service namespace and a token policy that our service is configured to trust: what we need to do is define a new issuer, which will represent the ADFSv2 authority, and create the appropriate rules that will process the incoming SAML token and produce access decisions.

  1. The exercise will modify an existing solution, which already contains a WCF service and its client ready for you to try. Open Microsoft Visual Studio 2008 with administrator privileges. From Start | All Programs | Microsoft Visual Studio 2008, right-click on Microsoft Visual Studio 2008 and select Run as administrator.
  2. Open the ACSWithSAML.sln solution file located in the %YourInstallationFolder%\Labs\IntroAccessControlService\Source\Ex02-UsingACSWithSAMLTokens\Begin folder.
  3. In Solution Explorer, Service project, double click on the Program.cs file. Update the ServiceNamespace value with the service namespace you chose in the first exercise, task 2, step 4.
    private
     const string ServiceNamespace = "{insert service namespace here}";
    
  4. We'll now need to update our service to recognize tokens issued according to the token policy we defined on the first exercise. To do this, first get the token policy key from ACS using the following command in a Powershell console (navigate to %YourInstallationFolder%\Labs\IntroAccessControlService\Source\Assets). Once the command finishes execution, select the key from the console and perform a right-click (this will put the highlighted text in the clipboard).
    .\acm.exe
     getall tokenpolicy
    

    Figure 2 Get the token policy key generated by ACS

  5. Paste the token policy key you just copied in the clipboard into the TokenPolicyKey constant on Service\Program.cs file.
    private const string TokenPolicyKey = "{insert token policy key here}";
    
  6. Run the local STS that will simulate an ADFSv2. To do this, open a command prompt as administrator on %YourInstallationFolder%\Labs\IntroToAccessControlService\Source\Ex02-UsingACSWithSAMLTokens\Assets and type LocalSTS.exe and keep it open for the rest of the exercise.

    Figure 3 Running LocalSTS tool

    Note:
     ADFSv2 is a server product with specific infrastructure requirements, among which there is Active Directory. Rather than forcing you to download, configure and manage a large virtual machine file, in this hands-on lab we will simulate an ADFSv2 instance with a custom STS written using Windows Identity Foundation. From the developer perspective the experience is absolutely equivalent: the instructions we give here would apply without any modification if the STS would be exposed by an ADFSv2 instance.

  7. Now you will configure our STS as a trusted issuer on ACS. ACS is able to automate a large part of the operation, simply by taking advantage of the metadata exposed by the STS itself. In this lab we provide a tool, FedMetadataClient, which will help you to send to the management service the opportune settings. Similarly to what you have done for the ACM tool in exercise 1, you need to configure the FedMetadataClient tool by opening the FedMetadataClient.exe.config file (from Labs\IntroToAccessControlService\Source\Ex02-UsingACSWithSAMLTokens\Assets) and assigning the right values to the management key and service name. You got those two values on Task 2 of the first exercise, on step 4 (when you chose the Service Namespace).
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
     <appSettings>
     <add key="stsBaseAddress" value="localhost/localsts"/>
    
     <add key="stspath" value="Trust/13/UserName"/>
     <add key="serviceNamespace" value="{insert service namespace here}"/>
     <add key="acsHostName" value="accesscontrol.windows.net"/>
     <add key="applies_to" value="http://localhost/weatherforecast"/>
    
     <add key="mgmtKey" value="{insert management key here}"/>
     </appSettings>
    </configuration>
    
  8. Run the FedMetadataClient.exe tool. To do this, open command prompt on %YourInstallationFolder%\Labs\IntroToAccessControlService\Source\Ex02-UsingACSWithSAMLTokens\Assets and type FedMetadataClient.exe

    Figure 4 Running FedMetadataClient from command line

  9. The FedMetadataClient tool retrieves all the relevant parameters from the STS and uses them against the management service to create an issuer. Let’s run the ACM tool to get the list of all issuers and verify that the new issuer has been correctly created. To do this, open a Powershell and run the following command.
    .\acm.exe getall issuer
    

    Figure 5 Get the issuer ID for the SAML issuer just created

  10. Now that you created a new issuer, you need to retrieve its Id so that you can reference it when you will create the new access rules. Look for the serviceNamespaceSAML issuer, and copy the id: iss_XXXXXX attribute. To do this, select the id using the mouse and right click to place the content on the clipboard. Paste it on the Labs\IntroToAccessControlService\Source\Ex02-UsingACSWithSAMLTokens\Begin\Setup.ps.txt file on the %adfsissuer% placeholder.
  11. You already defined a scope for your service: since you want to create a new rule in that scope, you need to retrieve the scope id in order to reference it. Run the ACM tool to get the scopes and lookup the scope for the weatherforecast service. To do this, open a Powershell and run the following command.
    .\acm.exe getall scope
    

    Figure 6 Get the scope id for the weatherforecast

  12. Look for the weatherforecast scope. Copy the id: scp_XXXXXX attribute. To do this, select the id using the mouse and right click to place the content on the clipboard. Paste it on the Setup.ps.txt file on the %scopeid% placeholder.
  13. Your new access control rule will be based on the content of the SAML token coming from the ADFSv2 instance of your trusted partner. Let’s say that you know that employees in the security group “Pilots” are meant to have access to the getforecast3days method: all you need to do is creating a rule that issues an action claim with value “Get3DaysForecast” whenever an incoming claim of type http://schemas.xmlsoap.org/claims/Group and value “Pilots” is received from this issuer. Paste in powershell the command from the setup file.
    .\acm.exe create rule -scopeid:%scopeid% -inclaimissuerid:%adfsissuer% -inclaimtype:http://schemas.xmlsoap.org/claims/Group -inclaimvalue:Pilots -outclaimtype:action -outclaimvalue:Get3DaysForecast
     -name:pilotsgroup3days
    

    Figure 7 Creating a rule to allow calling Get3DaysForecast

This is all you need to do for configuring your ACS Service Namespace. Next, we will explore the changes that the client must introduce for successfully obtaining a SWT token from ACS when presenting a SAML token: once that happens, it is business as usual for everything else.

Task 2 - Create an “ADFSv2 ready” client

In this task you will add to the client the code that retrieves a SAML token from the local STS, send it to ACS, get a SWT token back and finally call the service transmitting the SWT token in an HTTP header.

  1. In Solution Explorer, right click on the Client project and select Add Reference. Select the Microsoft.IdentityModel assembly and click OK. This will add the required assembly reference to use Windows Identity Foundation classes.

    Figure 8 Adding a reference to Microsoft.IdentityModel

  2. In Solution Explorer, Client project, double click on the Program.cs file. Add the following using directives at the beginning of the file for the code we'll be adding later.

    (Code Snippet – Introduction to ACS Lab - Ex02 Client Usings)

    namespace Client
    {
     using System;
     using System.Collections.Specialized;
     using System.IdentityModel.Tokens;
     using System.Linq;
     using System.Net;
    
     using System.ServiceModel;
     using System.ServiceModel.Security;
     using System.ServiceModel.Web;
     using System.Text;
     using System.Web;
     using Microsoft.IdentityModel.Protocols.WSTrust;
    
     using Microsoft.IdentityModel.Protocols.WSTrust.Bindings;
    

    The last two namespaces are from Windows Identity Foundation: we will use the classes found there for retrieving the SAML token from the local STS.

  3. Add the following constants in the body of the Program class.

    (Code Snippet – Introduction to ACS Lab - Ex02 Client constants)

    public class Program
    {
     private const string ServiceNamespace = "{insert service namespace here}";
     private const string AcsHostName = "accesscontrol.windows.net";
    
     private const string StsBaseAddress = "localhost/localsts";
     private const string StsPath = "Trust/13/UserName";
    
     public static void Main(string[] args)
    
  4. Update the Service Namespace (the one you entered in Task 2 of the first exercise, step 4) used in the client Client\Program.cs file. To do this, replace the ServiceNamespace constant in the code.
    private const string ServiceNamespace = "{insert service namespace here}";
    
  5. Add the following lines of code to call the local STS and receive a SAML token in return. The SAML token will then be sent to ACS to be exchanged with a SWT token.
    Note:
    Note: The order of these instructions aim at helping you understand what it takes for a client to be able to use ACS, hence it follows a top-down approach. The code won’t compile at this point, but it eventually will, once you will have followed the instructions in the next 3 steps

    (Code Snippet – Introduction to ACS Lab - Ex02 Client call STS)

    public static void Main(string[] args)
    {
     string stsAddress = string.Format("https://{0}/{1}", StsBaseAddress, StsPath);
     string acsSTSAddress = string.Format("https://{0}.{1}/WRAPv0.8",
     ServiceNamespace, AcsHostName);
    
     string samlAssertion = GetSamlAssertion(stsAddress, acsSTSAddress);
     string acsToken = GetACSToken(samlAssertion);
    
  6. Add the definition of the GetSamlAssertion method, which gets a token from the local STS. To do that, copy the following code at the end of the Program class.

    (Code Snippet – Introduction to ACS Lab - Ex02 GetSamlAssertion Method)

    ...
    public static void Main(string[] args)
    {
     ...
    }
    
    private static string GetSamlAssertion(string stsAddress, string acsStsAddress)
    {
    
     WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(
     new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential),
     new EndpointAddress(new Uri(stsAddress)));
    
     trustChannelFactory.TrustVersion
     = TrustVersion.WSTrust13;
    
     RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer);
     rst.AppliesTo = new EndpointAddress(acsStsAddress);
    
     rst.TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.Saml2TokenProfile11;
    
     WSTrustChannel channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
     GenericXmlSecurityToken token = channel.Issue(rst) as
     GenericXmlSecurityToken;
    
     return token.TokenXml.OuterXml;
    }
    
  7. The code here takes advantage of the object model for Windows Identity Foundation, and specifically of the WSTrustChannel class which makes it easy to send issuing requests to a WS-Trust STS.
  8. The code in step 5 makes use of the GetACSToken method, which gets a token from ACS based on the SAML token. Add the definition of GetACSToken by coping the following code at the end of Program class.

    (Code Snippet – Introduction to ACS Lab - Ex02 GetACSToken Method)

    public class Program
    {
     ...
    private static string GetSamlAssertion(string stsAddress, string acsStsAddress)
     ...
    
    private static string GetACSToken(string
     samlAssertion)
     {
     WebClient tokenClient = new WebClient();
     tokenClient.BaseAddress = string.Format("https://{0}.{1}", ServiceNamespace, AcsHostName);
    
     NameValueCollection values = new NameValueCollection();
    
     values.Add("wrap_SAML", samlAssertion);
     values.Add("applies_to", "http://localhost/weatherforecast");
    
     byte[] responseBytes = tokenClient.UploadValues("WRAPv0.8", values);
     string response = Encoding.UTF8.GetString(responseBytes);
    
    
     return response
     .Split('&')
     .Single(value => value.StartsWith("wrap_token=", StringComparison.OrdinalIgnoreCase))
     .Split('=')[1];
     }
    }
    

    You can see how the method body here closely resembles the code we used in exercise 1 for performing a similar function with a symmetric key.

  9. Add the code that will add the ACS token in the authorization HTTP header. To do that, add the following code right before calling the service.

    (Code Snippet – Introduction to ACS Lab - Ex02 WRAP Header)

    using (new OperationContextScope(proxy as IContextChannel))
    {
    string authHeaderValue = "WRAPv0.8" + " " + HttpUtility.UrlDecode(acsToken);
     WebOperationContext.Current.OutgoingRequest.Headers.Add("authorization",
     authHeaderValue);
    
     // call the service and get a response
     try
     {
    ...