Eric's Blog

Day to day experience in .NET
Welcome to Blogs @ IRM Sign in | Join | Help
 Search

Disclaimer

The content of this site is my own personal opinion and does not in any way represent my employer, it's subsideries or affiliates. These postings are provided "AS IS" with no warranties, and confer no rights.

This Blog

Let the SAML Token Flow

This post continues where the last one left off. We now have a SAML token that is re-serializable, which is good, because that will make it possible to use the same SAML token when calling a second business service from the first one. We could also easily check if we have a SAML token available by casting the current principal object to an ITokenPrincipal (see this post).
 
Now, what's left to do is to create a custom behavior that grabs the SAML token form the principal object, checks that the token is still valid, checks that the token are valid for the service that are going to be called, and in that case attaches that SAML token to the outgoing call. To be able to interact with WCF's client credentials the first step is to create a custom ClientCredentials class, so I created a TokenPrincipalClientCredentials class. The only purpose for this class is to be able to override the CreateSecurityTokenManager method so that we could return a custom ClientCredentialsSecurityTokenManager. The custom ClientCredentialsSecurityTokenManager only purpose is to override the CreateSecurityTokenProvider method, so that it is possible to provide a custom SecurityTokenProvider that checks the principal object for a valid SAML token. This means that we need to create three classes, where the first two only purpose is to let us inject the third one, which will do all the work. For the sake of completeness, here is the first two without some boilerplate code.
 
public class TokenPrincipalClientCredentials : ClientCredentials
{
    …

    public override SecurityTokenManager CreateSecurityTokenManager()
    {
        return new TokenPrincipalClientCredentialsSecurityTokenManager(this);
    }
}
internal class TokenPrincipalClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
    …
 
    public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
    {
        if (this.IsIssuedSecurityTokenRequirement(tokenRequirement))
        {
            IssuedSecurityTokenProvider baseProvider = (IssuedSecurityTokenProvider)base.CreateSecurityTokenProvider(tokenRequirement);
            TokenPrincipalSecurityTokenProvider provider = new TokenPrincipalSecurityTokenProvider(baseProvider);
            return provider;
        }
        else
        {
            return base.CreateSecurityTokenProvider(tokenRequirement);
        }
    }
 
    …
}
The reason that I first get the standard SecurityTokenProvider is that it would make it easier to implement the custom TokenPrincipalSecurityTokenProvider, so I create that class with the standard SecurityTokenProvider. Finally we can take a look at the class where the real action is (again stripped from some boilerplate code).
 
    internal class TokenPrincipalSecurityTokenProvider : IssuedSecurityTokenProvider, IDisposable
    {
        …
 
        protected override SecurityToken GetTokenCore(TimeSpan timeout)
        {
            ITokenPrincipal principal = System.Threading.Thread.CurrentPrincipal as ITokenPrincipal;
            SecurityToken securityToken = null;
 
            if (principal != null && !principal.Expired)
            {
                securityToken = principal.Token;
                //Check that the token is valid for the endpoint
                if (!ValidateAudienceRestrictions(securityToken as SamlSecurityToken))
                    securityToken = innerProvider.GetToken(timeout);
            }
            else
                securityToken = innerProvider.GetToken(timeout);
 
            return securityToken;
        }
 
        private bool ValidateAudienceRestrictions(SamlSecurityToken token)
        {
            if (token == null) return true;
 
            foreach (SamlCondition samlCondition in token.Assertion.Conditions.Conditions)
            {
                SamlAudienceRestrictionCondition audienceRestriction = samlCondition as SamlAudienceRestrictionCondition;
 
                if (audienceRestriction != null)
                {
                    bool uriFound = false;
                    string endpointUri = base.TargetAddress.Uri.ToString();
 
                    foreach (Uri uri in audienceRestriction.Audiences)
                    {
                        string uriString = uri.ToString();
                        if (endpointUri.StartsWith(uriString, StringComparison.OrdinalIgnoreCase))
                        {
                            uriFound = true;
                            break;
                        }
                    }
 
                    if (!uriFound)
                    {
                        System.Diagnostics.Trace.TraceWarning(Resources.Resource.SamlTokenNotValidForEndpoint, endpointUri);
                    }
 
                    return uriFound;
                }
            }
 
            //No SamlAudienceRestrictionCondition so it's ok to use the SamlToken
            return true;
        }
 
        …
    }
In the GetTokenCore method I check the current principal object for the SAML token, and If it's found, I try to validate it for the endpoint. To be able to determine if the SAML token should be used for the service that is being called I check the SAML conditions if there is a SamlAudienceRestrectionCondition and in that case if the Uri of the condition matches the Url that is being called.
 
The fact that the SamlAudienceRestrectionCondition is there is one of the features that I have added to my STS. It uses the same Uri that is configured in the config-file for the service token.
 
SamlConditions conditions = new SamlConditions(validFrom, validTo);
Configuration.ServiceTokenConfiguration serviceToken = SecurityTokenService.GetServiceToken(rst.AppliesTo);
if (serviceToken != null)
    conditions.Conditions.Add(new SamlAudienceRestrictionCondition(
        new List<Uri> { serviceToken.Uri }
    ));
 
Note how I instantiate the list with the new short syntax provided in Visual Studio 2008, so that I don't need to declare a new List and then add elements. This could of course be misused, but in cases like this when I need a list, containing only one item, I find it very handy.
 
To make this custom security token provider come in and play with WCF, all we have to do is to config a custom behavior for our endpoint on the client side.
 
<endpointBehaviors>
  <behavior name="xxx">
    <clientCredentials type="IRM.ServiceModel.Description.TokenPrincipalClientCredentials, IRM.ServiceModel.v1.0, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
    </clientCredentials>
  </behavior>
</endpointBehaviors>
Published den 18 december 2007 16:04 by ericqu

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Erics Blog said:

In my requirements for the STS implementation I wanted to be able to use the SAML token in the client

januari 30, 2008 14:56
 

Erics Blog said:

In my requirements for the STS implementation I wanted to be able to use the SAML token in the client

januari 30, 2008 14:59
 

Travis Spencer - Software Engineer said:

There is a lot of talk in the Geneva forum about caching and avoiding unnecessary round trips to the STS when calling downstream Web services. After all of it, I thought that reusing the same ChannelFactory&lt;T&gt; object would result in...

mars 31, 2009 09:44

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server, by Telligent Systems