Using the BootstrapContext property in .NET 4.5
Few minutes ago, while exchanging mails with some colleagues, I suddenly realized that although we gave various heads-ups about the change from BootstrapToken to BootstrapContext when moving from WIF v1 to the .NET Framework 4.5. we didn’t really provided the details of the new model in a search engine –friendly fashion.
We do have sample code that shows you how to use the new object model, however that’s cozily packed away in the July 2012 Identity Training Kit (lab WebSitesAndIdentity, exercise Ex3-InvokingViaDelegatedAccess) hence opaque to search engines. Well, let’s shine a bit of light on it with a brief Friday night’s post! 🙂
First of all: what the heck am I talking about? Let’s build a bit of context for the readers who didn’t land there while searching for “BootstrapContext” or “BootstrapToken in 4.5”.
With the classic WS-Federation authentication settings for a Web site, a user attempting to access a protected resource (aspx page, controller route, etc) gets redirected to one identity provider (your local ADFs, Windows Azure Active Directory, etc) to sign in. Once the user signs in, his/her browser session gets redirected back to the Web site by some javascript which triggers a POST that, among other things, contains the security token certifying the identity of the user and the successful outcome of the authentication operation. WIF deserializes and validates the token; if everything is as expected, the user is considered authenticated, a session cookie gets created and the content of the token is deserialized in a ClaimsPrincipal. Subsequent requests for Web site resources will be accompanied by the session token, which is enough for WIF to establish that the user is authenticated without the need to re-trigger the identity provider dance. Oh delightful surprise: we call a token that was used to create a session a bootstrap token. So far so good, right? Usual stuff.
Now: by default, the session cookie contains all the necessaire to reconstitute the ClaimsPrincipal at every postback. That includes, for example, the list of claims describing the user; that does NOT include, however, the token from the identity provider that originally carried the user’s claims. After all, the token absolved its function the moment it provided enough data for the Web site to validate it (signature from the expected issuer, validity timeframe, etc) and to obtain the required user claims. Once a token has been used for establishing a session, maintaining its raw bits would significantly bloat the size of the session cookie without obvious reasons: what counts at this point is the set of claims describing the user, how they got communicated is just an implementation detail. Or is it?
Although the above remains valid in the general case, there are few important exceptions. Sometimes the Web site will want to hold on the bits of the bootstrap token: the classic example is the case in which the Web site needs to invoke a backend service by flowing (in some capacity) the identity of its user, which in turn requires communicating the identity of said user to an external entity (for example, an STS). The bootstrap token already contains a nicely marshallable-through-boundaries (William Chaucer would NOT like me) representation of the user identity, and in fact various delegation flows (such as the famous ActAs you might have heard about) call for its use.
Fantastic! Now that I did due diligence and provided you some background about what the bootstrap token is and why you would want to access it in your app, we can get to the fun part: how to do it. I’ll just show you, and add the commentary afterwards.
First of all, you need to let WIF know that you want to opt in into saving the bootstrap token in the session cookie. You can accomplish this by flipping a switch in the config file:
<system.identityModel> <identityConfiguration saveBootstrapContext="true">
That’s pretty straightforward. The next fragment is a tad more involved.
Say that you got to the point in your code where you want to access the bootstrap token. Here there’s how you do it, formatted for your screens:
BootstrapContext bootstrapContext =
ClaimsPrincipal.Current.Identities.First().BootstrapContext
as BootstrapContext;
SecurityToken st = bootstrapContext.SecurityToken;
When you elect to save the bootstrap token, WIF will make it available to you in the BootstrapContext property of the first (and usually only) ClaimsIdentity of the identities collection stored by ClaimsPrincipal in the current thread.
You might be wondering: why all the casting, first to BootstrapContext and then to SecurityToken? Good eye, my friend! 🙂
In v1 we did have a property BootstrapToken, of type SecurityToken, which stored the token bits directly. We were able to offer that property because all classes were in the same assembly, hence we had no restrictions on types.
When we moved the WIF classes in .NET 4.5, we managed to put ClaimsPrincipal at the very core of .NET, mscorlib.dll. That has a long list of advantages, which I won’t restate here; however it also introduces some constraints. Case on point: the SecurityToken class now lives in a different assembly, System.IdentityModel.dll, which has to be explicitly referenced in order to be loaded. That lead to the decision of exposing the bootstrap token in a property of type Object, and to rely on you to make the appropriate casting operation. In my experience (I’ve been evangelizing WIF around the world for many years) the situations in which you need to get to the bootstrap token are not super common, and when they occur they are usually fairly advanced hence I hope that those two extra casting operations won’t add too much burden 🙂
Hope this helps!
Why does BootstrapContext.SecurityToken disappear, but BootstrapContext.Token appears, after an Apppool recycle? Then the ActAs sample fails…..
Hi Vittorio,
We had the same problem in our project.
We solved it by adding the following code to Global.asax
protected void Application_AuthenticateRequest(object sender, EventArgs eventArgs)
{
if(HttpContext.Current.Request.IsAuthenticated)
{
BootstrapContext bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;
if(bootstrapContext != null && bootstrapContext.SecurityToken == null)
{
FederatedAuthentication.WSFederationAuthenticationModule.SignOut();
}
}
}
@Sporre: Better solution for the user would be to recreate the SecurityToken from the assertion in the BootstrapContext. I was just wondering why it happens.
Hi Paul, Vittorio,
did you have any love with the BootstrapContext.SecurityToken disappearing – i’m seeing the same behaviour..
it works for the first one or two calls to the back end from the front end but then the security token shows as null.
when I try to recreate the security token (i’m not sure if im doing it right for JWT):
var handlers = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers;
JWTSecurityToken jwt = handlers.ReadToken(new XmlTextReader(new StringReader(bootstrapContext.Token))) as JWTSecurityToken;
(as you mentioned) it seams to have the session information but not the token from the idp so subsequent calls using “poorman’s” delegation fails.
any thoughts?
Confirm bug with a SecurityToken becoming a null.
Possible solution (thanks to a stackoverflow.com/…/wif-4-5-bootstrapcontext-security-token-null)
if (context.SecurityToken != null)
{
token = context.SecurityToken;
}
else if (!String.IsNullOrEmpty(context.Token))
{
var handlers = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers;
token = handlers.ReadToken(new XmlTextReader(new StringReader(context.Token)));
}
Thanks Danila, yes i’m still seeing what I think is a bug with the SecurityToken becoming a null.
using that possible solution doesn’t seam to work with JSON Web Tokens (JWT) because while the following code line creates a JWTSecurityToken from the bootstrapContext.Token
JWTSecurityToken jwt = handlers.ReadToken(new XmlTextReader(new StringReader(bootstrapContext.Token))) as JWTSecurityToken;
The token appears to be missing its signature and throws the following error if you try to validate it.
JWT10312: Unable to validate signature, JWT does not have a signature:
any ideas on how to get around this or is there a way to get the signature and concatenate it or does this compromise the users security?
any help appreciated!
To fix the null issue with SecurityToken, I modified the code in the About method from Vittorio’s example as follows:
var jwt = bc.SecurityToken as JWTSecurityToken;
string rawToken = jwt != null ? jwt.RawData : bc.Token;
It was validated on the back-end. Also, I had to update the cert to use the thumbprint of my X.509 in Azure.