Read claims principle from SignInResponseMessage of WSFederation

Since the release of WsFederation Owin middleware, the client using WSFederation based authentication becomes very easy

var options = new WsFederationAuthenticationOptions
             {
                    MetadataAddress = Constants.BaseAddress + "/wsfed/metadata",
                    Wtrealm = "urn:xxxxxxxx",
                    SignInAsAuthenticationType = "Cookies"
             };

            app.UseWsFederationAuthentication(options);


That is all.

Previously you would normally see a big chunk of configuration as following in web.config

<system.identityModel>
    <identityConfiguration>
      <claimsAuthenticationManager type="MVCClaims.Custom.CustomClaimsTransformer, MVCClaims"/>
      <claimsAuthorizationManager type="MVCClaims.Custom.CustomAuthorisationManager, MVCClaims"/>
      <!--Token valiation-->
      <audienceUris>
        <add value="http://yoururl.com /MVCClaims/"/>
      </audienceUris>
      <!-- Certificate validation
      This is the trust of STS
      .NET will automatically look at the thumbprint of the issuer in the token and check the value(s) against the one in web.config. 
      If the token was signed by a different X509 certificate then the token will be rejected
      The name of the issuer is the same as the Site ID we chose when we set up the STS. 
      It is important that the two values are the same, otherwise you will get a strange WIF exception: WIF10201: 
      No valid key mapping found for securityToken: ‘System.IdentityModel.Tokens.X509SecurityToken’ and issuer: 
      -->
      <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
        <authority name="xxxxSTS">
          <keys>
            <add thumbprint="852XCCCC3D302A1"/>
          </keys>
          <validIssuers>
            <add name=" xxxxSTS "/>
          </validIssuers>
        </authority>
      </issuerNameRegistry>
      <!--certificationValidationMode set to "None" by the the Identity and Access Tool for Visual Studio. For development purposes.-->
      <!--
      There’s an optional step after the token has been accepted by the web app, 
      i.e. after the token has passed the first level of scrutiny which is checking the thumbprint.
    Normally that initial check is enough for us to say that the token is real. 
    The keys element contains very strong statements about which certificates are the trusted ones. 
    In addition, however, we can perform an additional check on the certificate itself as follows:
    
    <certificateValidation certificateValidationMode="ChainTrust" revocationMode="Online" />  
    This will check if your certificate store includes a certificate with the same thumbprint and that it has not been revoked in the meantime. 
    ChainTrust means that .NET will check if the specified issuer, i.e. LocalSTS is available in your trusted CA folder in your certificate store. 
    revocationMode Online means that we want to go the CRL – Certificate Revocation List – 
    endpoint of the CA to see if the certificate has been revoked.
      -->
      <certificateValidation certificateValidationMode="None"/>
    </identityConfiguration>
  </system.identityModel>
  <!--
  By default the authentication session(cookie handler) feature will only work through SSL. This is well and good but may be an overkill for a local demo app
  -->
  <!---This defines the external login url -->
  <system.identityModel.services>
    <federationConfiguration>
      <cookieHandler requireSsl="false"/>
      <!--
      You’ll notice that the attributes ‘requireSsl’ and ‘requireHttps’ in the above XML are set to false. 
      This is acceptable for testing purposes but you should make sure to set have SSL ready for the production environment. 
      ‘passiveRedirectEnabled=true’ means that upon a 401 response the user is redirected to the login page of the STS, 
      i.e. at https://localhost/IdentityServer/issue/wsfed in this example. 
      The realm http://localhost:2532 will be attached to the query string as the ID of our web site.
      
      -->
      <wsFederation passiveRedirectEnabled="true" issuer="https://yoursts.com /issue/wsfed" realm="http://yoururl.com/MVCClaims/" requireHttps="false"/>
    </federationConfiguration>
  </system.identityModel.services>

I get that the MetadataAddress setting of  WsFederationAuthenticationOptions can retrieve all meta data from server like authentication endpoint, signing keys , issuer names etc, but what is lacking is we do not know what kind validation has been performed by minimum settings of  WsFederationAuthenticationOptions shown above.

I am aware that TokenValidationParameters of WsFederationAuthenticationOptions can be used to customize the validation process, and default settings for TokenValidationParameters  are

ValidateAudience = true

ValidateIssuer = true

But none of ValidAudience , ValidIssuer or ValidIssuerValidator is set, so is it checking Audience? Is it checking Issuer? Or even further how about the issuer key checking?

Using owin middle ware for WsFederation :  UseWsFederationAuthentication seems much simpler , but it is not helpful for us to understand what is going on behind scene for a secure WsFederation authentication,  with so many new authentication methods introduced by owin like Oauth and OpenId, it is very important  understand the concept of WsFederation and which things are key things in its protocol.

Here starting from a SignInResponseMessage, you will find what is in play of in process of  wsFederation authentication, and things learned here  I found are very critical in the understanding of the big picture.

You can find STS servers quite easily now adays, VS Studio comes with a local testing one, Azure manage portal can allow you to set up one in just minutes, I used Thinktecture.IdentityServer.v3

The client or in the language of WsFederation, a RP RelyingParty in this case is just simple MVC project(straight from VS MVC template), with no authentication at all.

Add following IndentityModel related dlls :

System.IdentityModel.dll

System.IdentityModel.Services.dll

System.IdentityModel.Tokens.ValidatingIssuerNameRegistry

Here is the code for About method of HomeController

if (!User.Identity.IsAuthenticated)
            {
                // This is a new request
                SignInRequestMessage mess = new SignInRequestMessage(
                    new Uri("http://yoursts.com/wsfed"),
                    "urn:yourrealm",
                    "http://localhost:10111/");
                return new RedirectResult(mess.WriteQueryString());

            }

What this does is if user is not logged in, then send a SignInRequestMessage to STS server, with three parameters: STS end point, realm, and the callback url, when STS finished authentication the claims will be posted back to callback url in SignInResponseMessage which you will see in a minute.

Realm identifies RelyingParty on STS server, the server will know what token type this relying party is using like SAML or JWT, the mapping claims for this RelyingParty, so a server can issue different claims to different RelyingParty.

Here the Index method of HomeController which processes the post back of SignInResponseMessage

if (Request.Unvalidated.Form["wresult"] != null) // another one is wa
            {
                // This is a response from the STS
                SignInResponseMessage mess = WSFederationMessage.CreateFromNameValueCollection(
                    WSFederationMessage.GetBaseUrl(Request.Url),
                    Request.Unvalidated.Form) as SignInResponseMessage;
                string tokenXml = mess.Result;

                XmlDocument xml = new XmlDocument();
                xml.LoadXml(tokenXml);

                IPrincipal principal = ReadSecurityToken(xml);
      
            }



So the whole security document  is found at Form[“wresult”], the convert it into SignInResponseMessage, the Result is the token xml

Here is the function ReadSecurityToken

private IPrincipal ReadSecurityToken(XmlDocument signInResponseXml)
        {
            // for the security token to be read by the handler, the root of the XML must be this line:
            // <Assertion ID="..." IssueInstant="..." Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">

            string samlTokenXml = signInResponseXml
                .DocumentElement  // <trust:RequestSecurityTokenResponseCollection>
                .ChildNodes[0] // <trust:RequestSecurityTokenResponse>
                .ChildNodes[1] // <trust:RequestedSecurityToken>
                .InnerXml; // <Assertion>

            var xmlTextReader = new XmlTextReader(new StringReader(samlTokenXml));

            var config = new SecurityTokenHandlerConfiguration();

            // Realm, RP
            config.AudienceRestriction.AllowedAudienceUris.Add(new Uri("urn:yourrealm "));

            // ValidatingIssuerNameRegistry is in a nuget package with same name

            IssuingAuthority issuingAuthority = new IssuingAuthority("mysts");
            //it is case sensitive, in MMC, it thumprints might be low case and with spaces
            //it is not necessary the certificate to be installed locally, it is only checking 
            // the thumbprints here to be sure that the provided certificate is the correct one

            issuingAuthority.Thumbprints.Add("852XCCCC3D302A1");
            //	TokenIssuerName 
            issuingAuthority.Issuers.Add("https://yoursts.com");
         
            config.IssuerNameRegistry = new ValidatingIssuerNameRegistry(issuingAuthority);


            var securityTokenHandlers = SecurityTokenHandlerCollection.
                CreateDefaultSecurityTokenHandlerCollection(config);


            // read the token
            SecurityToken securityToken = securityTokenHandlers.ReadToken(xmlTextReader);

            if (securityToken != null)
            {
                var claims = securityTokenHandlers.ValidateToken(securityToken);
                IPrincipal principal = new ClaimsPrincipal(claims);

                return principal;
            }

            return null;
        }

Basic process is create securitytokenhandlers first , then set the validation rules in its configuration then use seuritytokenhandlers to read the token, claims principle is returned as result of Validation of token by securitytokenhandlers

A few tricky places:

  1. The SecurityTokenHandlers only recognize a token from <Asserttions> element
  2. The minimum validation SecurityTokenHandlers must perform is Audience which is Realm, and IssuingAuthority
  3. IssuingAuthority is done through IssuerNameRegistry, in the instance of ValidatingIssuerNameRegistry, there are other IssuerNameRegistry classes available, one is used by xml configuration ConfigurationBasedIssuerNameRegistry.
  4. IssuingAuthority has an issuer name and thumbprint of signing certificate, one of benefits of new IssuerNameRegistry  is you can add multiple pairs of issuer and thumbprint, as long as they are paired up
  5. Thumbprint is here to just check the certificate in the token is the allowed one, so it does not need the certificate to be fully present from client , aka you do not have to install the server certificate on client as long as you know the thumbprint. As a securer way, you might want to install the whole certificate(no need of private key part, as this is signing certificate) on client and do a comparison of whole certificate, so it is client decision how far this needs to go. Here only needs to compare thumbprint. If you copied the thumbprint from MMC, you need to remove all the spaces between them, and they must be all capitalized, the comparison is case sensitive.
  1. When you get the claims principle, you can just signin the claims principle with the cookiename if you are using owin cookie authentication

Tags:

This entry was posted on Tuesday, July 29th, 2014 at 12:53 am and is filed under ASP.NET. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply

*