February 21st, 2012

Request a token from ADFS using WS-Trust from iOS, Objective-C, IPhone, IPad, Android, Java, Node.js or any platform or language

This is not just a SEO friendly name, in this post I want to show you a very easy way of providing Active Directory authentication in your apps, no matter the platform or language that you use, the only requirement is to be able to make an http post.

Request for a Security Token

To talk with ADFS we must be able to speak WS-Trust protocol, on the .NET platform this is a very easy thing to do thanks to WCF and Windows Identity Foundation frameworks, but regardless the platform make a WS-Trust call is not so hard.

The first thing that we need to know is that WS-Trust protocol defines an standard way of requesting security tokens, based on an XML structure known as Request Security Token or RST, this is an example of that structure:

<trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>https://yourcompany.com</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
<trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
</trust:RequestSecurityToken>

Focusing on the basics, there is a couple of fields that are important to us, inside of the RequestSecurityToken element you will find the AppliesTo tag where, using the WS-Addressing standard, we define the scope to which the token is valid, in this case: https://yourcompany.com.

RequestType specifies the action that you want to execute, in our case “Issue”, this means that we want that the (Security Token Service) STS issue a new token, but another option could be renewed an already issued token, in that case the RequestType would be “Renew”.

Finally, the TokenType specifies the type of the token that you want, in our case we are asking for a token based on the SAML 2.0 format.

Doesn’t looks very hard, isn’t? but where do we say who we are? well, one detail that adds a bit of complexity is the fact that all the WS-* protocols stack is build on top of SOAP, so we need to speak SOAP in order to send the token request. Once more, speak SOAP is not so hard, SOAP is also XML-Based, I’m not going to explain the whole SOAP protocol, but you can find the format for a soap message here: http://www.w3.org/2003/05/soap-envelope/

In our case, to talk with ADFS from a native client we going to use username and password security, so this is how the SOAP message will looks like: (I’ve cut some arguments to improve the presentation)

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
            xmlns:a="http://www.w3.org/2005/08/addressing"
            xmlns:u="...">
  <s:Header>
    <a:Action s:mustUnderstand="1">

http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue

    </a:Action>
    <a:To s:mustUnderstand="1">https://yourcompany.com/adfs/services/trust/13/UsernameMixed</a:To>
    <o:Security s:mustUnderstand="1" mlns:o="...">
      <o:UsernameToken u:Id="uuid-6a13a244-dac6-42c1-84c5-cbb345b0c4c4-1">
        <o:Username>Leandro Boffi</o:Username>
        <o:Password Type="...">P@ssw0rd!</o:Password>
      </o:UsernameToken>
    </o:Security>
  </s:Header>
  <s:Body>
    <trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
      <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
        <a:EndpointReference>
          <a:Address>https://yourcompany.com</a:Address>
        </a:EndpointReference>
      </wsp:AppliesTo>
      <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
      <trust:RequestType>

http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue

      </trust:Requesthttps://yourcompany.com/adfs/services/trust/13/UsernameMixedType>
      <trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
    </trust:RequestSecurityToken>
  </s:Body>
</s:Envelope>

To quickly understand the format, the SOAP envelop has two main tags: header and body. The body of our message contains the RST (Request for Security Token) message that we created before. In the header we can find context parameters like, the Uri of the service endpoint (To), the name of the action exposed in that endpoint that you want to execute (Action), remember that in SOAP you can have multiple actions in a single endpoint, and who we are (Security), in this case username and password.

To use UserName and Password authentication we need to look for the action Issue in the endpoint https://yourcompany.com/adfs/services/trust/13/UsernameMixed, so make sure that this endpoint is enabled on ADFS configuration.

Once we have the SOAP message, we just need to send it to the server using a regular HTTP POST, this is an example of how to do it on .NET, but it can be applied to any platform or language:

var client = new WebClient();

client.Headers.Add("Content-Type", "application/soap+xml; charset=utf-8");

var result = client.UploadString(
        address: "https://yourcompany.com/adfs/services/trust/13/UsernameMixed",
        method: "POST",
        data: soapMessage);

Make sure that you specify the Content-Type header to “application/soap+xml; charset=utf-8”, what you finally need to send to the server is this:

POST /adfs/services/trust/13/UsernameMixed HTTP/1.1

Connection: Keep-Alive
Content-Length: 1862
Content-Type: application/soap+xml; charset=utf-8
Accept-Encoding: gzip, deflate
Expect: 100-continue
Host: localhost

<s:Envelope ...> ... </s:Envelope>

I’ve added other headers to be consistent with the HTTP Protocol, but for ADFS just the Content-Type is required.

The Answer: Request Security Token Response

If your credentials were valid, and the scope Uri is the right one, you will get a SOAP response from ADFS. In the body of that message you will get something like this:

<trust:RequestSecurityTokenResponseCollection xmlns:trust="...">
  <trust:RequestSecurityTokenResponse>
    <trust:Lifetime>...</trust:Lifetime>
    <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">...</wsp:AppliesTo>
    <trust:RequestedSecurityToken>
      <Assertion ID="_fcf06a39-c495-4074-8f22-4a7df6e26513"
                 IssueInstant="2012-02-21T04:27:24.771Z"
                 Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <Issuer>http://yourcompany.com/adfs/services/trust</Issuer>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
          <ds:SignedInfo>
            <ds:CanonicalizationMethod
              Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod
              Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#_fcf06a39-c495-4074-8f22-4a7df6e26513">
              <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
              </ds:Transforms>
              <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
              <ds:DigestValue>...</ds:DigestValue>
            </ds:Reference>
          </ds:SignedInfo>
          <ds:SignatureValue>...</ds:SignatureValue>
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <ds:X509Data>
              <ds:X509Certificate>...</ds:X509Certificate>
            </ds:X509Data>
          </KeyInfo>
        </ds:Signature>
        <Subject>
          <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
            <SubjectConfirmationData NotOnOrAfter="2012-02-21T04:32:24.771Z"/>
          </SubjectConfirmation>
        </Subject>
        <Conditions NotBefore="2012-02-21T04:27:24.756Z" NotOnOrAfter="2012-02-21T05:27:24.756Z">
          <AudienceRestriction>
            <Audience>https://yourcompany.com/</Audience>
          </AudienceRestriction>
        </Conditions>
        <AttributeStatement>
          <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
            <AttributeValue>Leandro Boffi</AttributeValue>
          </Attribute>
          <Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role">
            <AttributeValue>Administrator</AttributeValue>
            <AttributeValue>Mobile User</AttributeValue>
          </Attribute>
        </AttributeStatement>
        <AuthnStatement AuthnInstant="2012-02-21T04:27:24.724Z">
          <AuthnContext>
            <AuthnContextClassRef>
              urn:oasis:names:tc:SAML:2.0:ac:classes:Password
            </AuthnContextClassRef>
          </AuthnContext>
        </AuthnStatement>
      </Assertion>
    </trust:RequestedSecurityToken>
    <trust:RequestedAttachedReference>...</trust:RequestedAttachedReference>
    <trust:RequestedUnattachedReference>...</trust:RequestedUnattachedReference>
    <trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
    <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
    <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
  </trust:RequestSecurityTokenResponse>
</trust:RequestSecurityTokenResponseCollection>

This format is also specified in the WS-Trust protocol as “Request Security Token Response” or RSTR, but for you the most important section of the response is in:

RequestSecurityToeknResponseCollection/RequestSecurityToeknResponse/RequestedSecurityToken

The content of that tag is the security token, in our case a SAML 2.0 token:

<Assertion ID="_fcf06a39-c495-4074-8f22-4a7df6e26513" IssueInstant="2012-02-21T04:27:24.771Z"
                 Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>http://yourcompany.com/adfs/services/trust</Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
      <ds:Reference URI="#_fcf06a39-c495-4074-8f22-4a7df6e26513">
        <ds:Transforms>
          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
        </ds:Transforms>
        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <ds:DigestValue>...</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>...</ds:SignatureValue>
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <ds:X509Data>
        <ds:X509Certificate>...</ds:X509Certificate>
      </ds:X509Data>
    </KeyInfo>
  </ds:Signature>
  <Subject>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <SubjectConfirmationData NotOnOrAfter="2012-02-21T04:32:24.771Z"/>
    </SubjectConfirmation>
  </Subject>
  <Conditions NotBefore="2012-02-21T04:27:24.756Z" NotOnOrAfter="2012-02-21T05:27:24.756Z">
    <AudienceRestriction>
      <Audience>https://yourcompany.com/</Audience>
    </AudienceRestriction>
  </Conditions>
  <AttributeStatement>
    <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
      <AttributeValue>Leandro Boffi</AttributeValue>
    </Attribute>
    <Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role">
      <AttributeValue>Administrator</AttributeValue>
      <AttributeValue>Mobile User</AttributeValue>
    </Attribute>
  </AttributeStatement>
  <AuthnStatement AuthnInstant="2012-02-21T04:27:24.724Z">
    <AuthnContext>
      <AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:Password
      </AuthnContextClassRef>
    </AuthnContext>
  </AuthnStatement>
</Assertion>

Once we extract the token from the response, everything gets simpler: Inside of the AttributeStatment section you will have a list of Attribute, this are the claims, information of the user, for example in this token we have three different claims:

  • Type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
  • Value: Leandro Boffi
  • Type: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
  • Value: Administrator
  • Type: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
  • Value: Mobile User

You can use those claims to perform authorization in your application, but also if your app needs to call webservices that rely on your ADFS, you will need to send the entire token in each request that you made to those services (I’ll explain this scenario in a future post).

Security Features

The token has some security features with which we can get us to make our application more secure. I’m not going to explain all the features in this post, but for example, if we want we can verify that no body has modified the token, because it is signed by the issuer (in our case, ADFS). You can find the signature on Assertion/Signature/SignatureValue. This signature is also based on a standard called XML Signature, you can find the specification here: http://www.w3.org/Signature/.

Also another very important feature is the fact that the token has a limited life time, to avoid that somebody use an old token, you can find that in the Assertion/Conditions/NotBefore and NotOnOrAfter.

Conclusion

Integrate the identity of our apps to Active Directory, no matter the platform or the language is possible due to ADFS is based on WS-Trust an standard protocol. If your language do not support WS-Trust natively it requires a bit more of effort, but as we saw in this post it’s not hard at all, you just need an XML template for the SOAP+RST call and an HTTP Post.

Download here the template for doing the SOAP-RST call, just replace the values in brackets with your values and start requesting tokens!

Hope has been useful!

  • prem nair

    We are trying to do an authentication using ADFS in our Cordova Project and suddenly Android KitKat started behaving weird. it works sometime and sometimes not. Any ideas?

    • http://leandrob.com leandrob

      It’s hard to say with that level of information, if you email me me@leandrob.com maybe I can help you!