Share via


Issued Tokens Renewal : Get your token expiration time set correctly by STS

Working with issued token is always fun. The whole possibility of making 3 components (client, RP and STS) to work seamlessly excites me to no end. Of late I worked on an interesting token renewal issue where the client was not requesting a new SAML token from the STS, even after expiration. An effect of that was client authentication failure at the service end as the presented token had already expired.

Exception thrown by the service:

<ExceptionType>System.IdentityModel.Tokens.SecurityTokenException, System.IdentityModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

<Message>The SamlToken is not time valid. The current time '9/22/2009 6:54:35 PM' is outside the Effective '9/22/2009 6:53:54 PM' and Expiration '9/22/2009 6:54:24 PM' time of the token.</Message>

<StackTrace>

at System.IdentityModel.Selectors.SamlSecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)

at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)

at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver, IList`1 allowedTokenAuthenticators, SecurityTokenAuthenticator&amp; usedTokenAuthenticator)

at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout)

at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)

</StackTrace>

Initial thought was that a lifetime of a SAML token was governed by the attributes set inside <saml:Condition> element :

           <saml:Conditions NotBefore="2009-09-22T18:53:54.425Z"

                                     NotOnOrAfter="2009-09-22T18:54:24.425Z"></saml:Conditions>

 

Once de-serialized at the client, the ‘validTo’ and ‘validFrom’ properties of the returned SecurityToken at the client should reflect the same values. However in my case, things were a bit different:

SecurityToken.ValidFrom à DateTime.UtcNow

       SecurityToken.ValidTo à DateTime.MaxValue

 

This was really disconcerting. Where the heck were these values coming from ? A max value for ValidTo meant that the issued token would never be renewed !!! Had to dig into the reflected source code and figure out the root cause of the issue. Take a look at the method responsible for de-serializing a RSTR at the client. Just showing the lines of interest from System.ServiceModel.Security.WSTrust+Driver.CreateRequestSecurityTokenResponse:

DateTime utcNow = DateTime.UtcNow;

DateTime maxUtcDateTime = DateTime.MaxValue;

if ((element2.LocalName == this.DriverDictionary.Lifetime.Value) && (element2.NamespaceURI == this.DriverDictionary.Namespace.Value))

{

XmlElement element4 = XmlHelper.GetChildElement(element2, "Created", "https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

if (element4 != null)

{

utcNow = DateTime.ParseExact(XmlHelper.ReadTextElementAsTrimmedString(element4),WSUtilitySpecificationVersion.AcceptedDateTimeFormats, DateTimeFormatInfo.InvariantInfo,DateTimeStyles.None).ToUniversalTime();

}

XmlElement element5 = XmlHelper.GetChildElement(element2, "Expires", "https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

if (element5 != null)

{

maxUtcDateTime = DateTime.ParseExact(XmlHelper.ReadTextElementAsTrimmedString(element5),WSUtilitySpecificationVersion.AcceptedDateTimeFormats,DateTimeFormatInfo.InvariantInfo,DateTimeStyles.None).ToUniversalTime();

}

}

Note the highlighted elements above. WCF looks for these elements inside an incoming RSTR to set the lifetime of a security token, NOT inside SAML token. If not present, these individual values, ValidTo and ValidFrom, are set to their corresponding values as highlighted earlier. While the solution to this issue is very simple, I will come to that a bit later. First, we need to understand the significance of such an implementation. A saml:Condition defines the lifetime of a SAML token. A lifetime element defines the lifetime of a RSTR received by the client. This makes perfect sense since an issued token can be something other than a SAML token as well. Taking that into light, a token lifetime at the client and hence any subsequent renewal decision should not be tightly coupled with a SAML token lifetime.

Coming back to the solution, just ensure that you define a lifetime element when generating a RSTR inside your custom STS. A lifetime element inside a RSTR looks as follows:

 

<Lifetime>

<Created xmlns="**https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd**"\>**2009-09-22T19:03:44.4697845Z**\</Created>

<Expires xmlns="**https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd**"\>**2009-09-22T19:03:54.4697845Z**\</Expires>

</Lifetime>

A token with the above lifetime element will be valid for 10 minutes before a renewal request is made by the client to STS.

Now let’s make things a bit more interesting by considering these 2 elements in union. Consider a generated security token with the following values set:

saml:Conditions NotBefore : 2009-09-22T19:03:44.4697845Z

saml:Conditions NotOnOrAfter : 2009-09-22T19:03:49.4697845Z

Lifetime Created : 2009-09-22T19:03:44.4697845Z

Lifetime Expires : 2009-09-22T19:03:54.4697845Z

This means that while the lifetime of a SAML token is set to 5 minutes, that of the RSTR is set to 10 minutes. So the client will not renew the token till the 11th minute, while the SAML token will be invalidated by the service starting from the 6th minute. Make sure the lifetime of the issued security token by STS <= lifetime of SAML token to prevent such a scenario.

There is one very important parameter which I have not spoken of in this article: binding. In this explained scenario, secure sessions were disabled. Things change when we enable that either through binding or use one which implicitly sets up a secure session (netTcpBinding). I will talk about that in my following post. Till then enjoy playing around with issued tokens.