DCSIMG
Convert SAML token to SWT token using ACS - Manu Cohen-Yashar's Blog

Manu Cohen-Yashar's Blog

Convert SAML token to SWT token using ACS

In Claim based applications we use token to provide the application (Relying party) with details (a collection of claims) about the the authenticated identity.

In ASP.net web sites and WCF SOAP services SAML tokens are used as a container for the claims. SAML is a standard that describe how token and claims are constructed and how they are cryptographically protected using digital signature and encryption. SAML tokens are powerful yet they are large. The size of the token is not a real issue in ASP.Net web sites as well as in SOAP WCF services but for REST web services it is a problem.

The solution is a simpler and shorter token called SWT token (Simple Web Token). SWT is a simple header we attach to the http request sent to our REST service. The header contains a set of key value pairs. Of course SWT have a format we need to follow. It is protected with SHA256 HMACs and follow the spec that can be found here.

Constructing the token is not complicated but it is tedious. AppFabric Access Control Service can help here. It can transform SAML tokens to SWT tokens or create brand new SWT tokens for users it manages. (see the following sample)

To transform SAML tokens to SWT tokens we first need to get a SAML token. To do that we need to build a custom STS or a use ADFS 2.0. (In future posts I will not describe how to build a custom STS.)

Now we need to go to the ACS portal and create a configuration for our application.
we have to create a namespace for access control service if we haven't done that previously and then press "access control service"

image

Now we define an Identity provider (our custom STS that produce SAML tokens), a relying party (our application) for which we define that we want to generate SWT tokens, 

image

and finally we define rule groups that defines how to construct the SWT token based on the information provided by the SAML token provided.

Now our application needs to call the custom STS and retrieve the SAML Token.

 /// <summary>
 /// Call an STS and get a SAML Token
 /// </summary>
 // <param name="stsAddress">The name of the sts that generates the SAML token</param>
 /// <param name="acsStsAddress">The ACS endpoint that will accept the SAML token that will be generated</param>
 /// <param name="username">username with which the client authenticate to the STS</param>
 /// <param name="password">password with which the client authenticate to the STS</param>
 /// <returns></returns>
 public static string GetSamlAssertion(string stsAddress, string acsStsAddress, string username, string password)
 {
   var trustChannelFactory = new WSTrustChannelFactory("CustomSTSEP");
   trustChannelFactory.Credentials.UserName.UserName = username;
   trustChannelFactory.Credentials.UserName.Password = password;
 
   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; }
//this is the configuration I used:
<client>
      <endpoint name = "CustomSTSEP"
                address="https://localhost/MySTS/activeSTS.svc/username_over_transport"
                binding="ws2007HttpBinding"
                bindingConfiguration="IdentityProviderBindingConfiguration"
                contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustChannelContract">       
      </endpoint>
</client>
<bindings>
      <ws2007HttpBinding>
        <binding name="IdentityProviderBindingConfiguration">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="UserName" establishSecurityContext="false" />
          </security>
        </binding>
      </ws2007HttpBinding>
 </bindings>
 <behaviors>
      <endpointBehaviors>
        <behavior name="clientBehavior">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="None"/>
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
 </behaviors>

Once we hold the SAML Token we can send it to the ACS and get the SWT token we need.

/// <summary>
/// Get a SWT Token from Appfabric ACS using a SAML token that was previously generated
/// </summary>
/// <param name="scope">The realm of the application (RP) to which the token is generated</param>
/// <param name="samlAssertion">The SAML token that will be transformed to SWT token</param>
/// <returns></returns>
public static string GetSwtTokenFromACS(string scope, string samlAssertion)
{
  // request a token from ACS   WebClient client = new WebClient();   client.BaseAddress =
string.Format("https://{0}.{1}", ACSNamespace,
"accesscontrol.windows.net");   var values = new NameValueCollection   {       { "wrap_assertion_format", "SAML"},       { "wrap_assertion", samlAssertion },       { "wrap_scope", scope }   };   byte[] responseBytes = null;   try   {        responseBytes = client.UploadValues("WRAPv0.9/", "POST", values);   }   catch (WebException ex)   {       var reader = new StreamReader(ex.Response.GetResponseStream());       Logger.Instance.WriteCritical(ex, reader.ReadToEnd());                  }   string response = Encoding.UTF8.GetString(responseBytes);  return HttpUtility.UrlDecode(                 response                 .Split('&')                 .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))                 .Split('=')[1]); }

Now we can call our REST service…

 WebClient client = new WebClient();  string headerValue = string.Format("WRAP access_token=\"{0}\"", swttoken);  client.Headers.Add("Authorization", headerValue);  Stream stream = client.OpenRead(address);

Enjoy

Manu

 

Comments

No Comments

Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: