How to convert a SessionSecurityToken to a JWTSecurityToken?

Category: azure security

Question

A Bit of Help on Mon, 20 May 2013 21:17:05


Hello,

Our application use the following: ASP.NET MVC4, WebAPI, Azure ACS, JWTSecurityToken, and OAuth 2.  After the user authenticates in our MVC application, we need to pass a JWTSecurityToken access token to our WebAPI service with each call to it.  All of the information that we need is in the SessionSecurityToken, but we need to figure out a way to convert it to JWTSecurityToken so we can use the .RawData() method to add it to the Authorization Header.  I have not been able to figure out how to make this happen.  Perhaps, I need to create another JWTSecurityToken using the parts in the SessionSecurityatoken.  Is there an easy way to

Whenever a token is received, we may update its ValidTo and ValidFrom fields, as in the following SessionAuthenticationModule_SessionSecurityTokenReceived().  It works great... 

Whenever we need to make a call to the WebAPI service, we need to set the Authorization Header with what is is the SessionSecurityToken, but send it as a JWTSecurityToken.  This is where I am having trouble...  What I want is to simply get the SessionSecurityToken, convert it to a JWTSecurityToken, and then call the .RawData() method to load it into the Authorization Header.  Alas, I am stuck...  I couldn't figure out how to do the conversion, so I created CreateSlidingAccessSessionToken(), which follows.  Unfortunately, it does not work properly... Since I create a new JWT token, RawData is null and the token is unsigned. :(  I have spent hours on this and hope that someone can give me a nudge in the correct direction.

Thanks for your time and help,

Mike

        void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
        {
            DateTime now = DateTime.UtcNow;
            DateTime validFrom = e.SessionToken.ValidFrom;
            DateTime validTo = e.SessionToken.ValidTo;
            if (now > validFrom.AddMinutes((validTo.Minute - validFrom.Minute) / 2))
            {
                SessionAuthenticationModule sam = sender as SessionAuthenticationModule;
                e.SessionToken = sam.CreateSessionSecurityToken(e.SessionToken.ClaimsPrincipal, e.SessionToken.Context,
                now, now.AddMinutes(2), e.SessionToken.IsPersistent);
                e.ReissueCookie = true;
            }           
        }

        public static JWTSecurityToken CreateSlidingAccessSessionToken()
        {
            // Get the session security token for the current HTTP session.
            var sst = FederatedAuthentication.SessionAuthenticationModule.ContextSessionSecurityToken;

            // Get the bootstrap security token.  It will not have changed since initial authentication.
             BootstrapContext bc = ClaimsPrincipal.Current.Identities.First().BootstrapContext
                    as BootstrapContext;

            // Create a temporary JWT from the bootstrap token...           
            JWTSecurityToken tempJwt = bc.SecurityToken as JWTSecurityToken;
            
            // To avoid duplicate claims, we will only keep the ones that begin with http
            // in the temporary JWT token.
            List<Claim> claims = null;
            claims = (from item in tempJwt.Claims
                     where item.Type.StartsWith("http",StringComparison.InvariantCultureIgnoreCase)
                     select item).ToList();

            // Create our final JWT token that has the correct claims and validation times...  Sliding session...
            JWTSecurityToken jwt = new JWTSecurityToken(tempJwt.Issuer, tempJwt.Audience, claims, tempJwt.SigningCredentials, sst.ValidFrom, sst.ValidTo);
            
            return jwt;
        }




Replies

Qin Dian Tang - MSFT on Tue, 21 May 2013 03:38:37


Hi,
 
I am trying to involve someone familiar with this topic to further look at this issue. There might be some time delay. Appreciate your patience.
 
Thanks,

Paul Blair on Tue, 21 May 2013 06:34:13


Hi Mike,

I'm afraid I can't offer much help as I am in exactly the same situation i.e. trying to implement a solution based on ASP.NET MVC4, WebAPI, Azure ACS, JWTSecurityToken, and OAuth 2. 

I also tried implementing this solution: http://www.cloudidentity.com/blog/2013/01/09/using-the-jwt-handler-for-implementing-poor-man-s-delegation-actas/ but run into the problem where the bootstrapcontext is null as there no identities passed.

It's one of the problems where it always feels you are close but has ended up soaking up plenty of hours for me also. Anyway I will be continuing to work on it this week and will report back if I make any progress.

Cheers

Paul

A Bit of Help on Tue, 21 May 2013 14:14:26


Hi Paul, Thanks for joining this conversation. I suspect that there are a lot of people trying to accomplish this task... I think that we will need to create a new JWTSecurityToken using info from the session security token, but after a few hours I have not been able to get it to work properly,,, traveling today, so hopefully a break will bring new ideas! Perhaps switching to Thinktecture's JWT, but then we have to convert from the ACS version to The other version.... Arggg...

MingXu-MSFT on Tue, 21 May 2013 14:31:56


Hi,

  >> Whenever a token is received, we may update its ValidTo and ValidFrom fields, as in the following SessionAuthenticationModule_SessionSecurityTokenReceived(). 

Based on my understanding, if you want to change valid from/to, then it is needed to modify your STS configuration. Let STS send you a token with the valid from/to you want to use. We'd better not to modify the token yourself. In ACS, this is configured via token lifetime. You can refer to http://msdn.microsoft.com/en-us/library/windowsazure/gg185906.aspx#BKMK_5 for more details.
Security tokens are designed so that it is very difficult for anything other than the STS to modify them(Consider the security factors).
On the server side, it is needed to send the token sent by the client to the STS to verify it is indeed generated by the STS. To do this with ACS, you can check the hmac. ACS uses the key you configured on the portal to generate a hmac for the token. Just ACS is able to generate the hmac(unless the key is leaked to outside). I would like to suggest you to check http://msdn.microsoft.com/en-us/library/windowsazure/hh289317.aspx for how to verify the token.

Best Regards,

Ming Xu

A Bit of Help on Wed, 22 May 2013 03:51:41


Hi Ming,

Thank you for your response...  I understand what you have said and need to think about it...  My initial reaction is that I want to avoid all of the potential calls back to ACS that could happen for each action from users of our multitenant SaaS application.  This is why I am trying to work with the security token that I mentioned.  It seems to me that if I want to proceed in the direction that I've described, then I will need to create a new JWTSecurityToken from the information in the SessionSecurityToken.  The new JWT token can then be used in the auth header, as we need. 

Quick question...  What action causes the RawData field in the JWTSecurityToken to become populated?  Is it populated after a successful validation?  If I can find an answer to this question, it will bring me closer to an solution.

Thanks,

Mike

P.S. Is the JWTSecurityToken source code open source so I can see how things are implemented?  If so, where can I find the code? Thanks!

MingXu-MSFT on Mon, 27 May 2013 08:20:48


Hi,

  >> What action causes the RawData field in the JWTSecurityToken to become populated?

JWTSecurityToken is a class that contains the token's information. JWTSecurityTokenHandler creates a new instance of  JWTSecurityToken when validating the token, and use the constructor that accepts a single string parameter (encoded string) that populates RawData. If you're building your own security token handler, you can construct the JWTSecurityToken yourself. Please make sure you use the simplest constructor if you want to populate RawData. 

  >>  Is the JWTSecurityToken source code open source so I can see how things are implemented?  If so, where can I find the code?

As I cannot find the source code on Nuget, I am not sure whether the package is open source or not.

Best Regards,

Ming Xu.

A Bit of Help on Wed, 29 May 2013 01:33:16


Here is what I've created and my initial testing has been successful...

Basically, I assume that you are adjusting the valid time period of the SessionSecurityToken.  Its data will be used to create a new JWT.  This JWT is validated, and parsed by the handler, which will cause its RawData field to be populated.  Finally, you can write the RawData value to the Authorization Header so your WebAPI service can do its validation.  I hope that this will help others...

            // Get the session security token for the current HTTP session.  Its valid time period
            // will slide.
            var sst = FederatedAuthentication.SessionAuthenticationModule.ContextSessionSecurityToken;

            // To avoid duplicate claims when we create our JWT, we will only keep the ones that begin with http.
            List<Claim> claims = null;
            claims = (from item in sst.ClaimsPrincipal.Claims
                      where item.Type.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)
                      select item).ToList();

            // Generate symmetric key for HMAC-SHA256 signature
            RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider();
            byte[] keyForHmacSha256 = Convert.FromBase64String("+blahblahblahblahblah=");

            // Create our JWT from the session security token
            JWTSecurityToken jwt = new JWTSecurityToken
            (
                "https://abcd.accesscontrol.windows.net/",
                "unc:abcd",
                claims,
                new SigningCredentials(
                    new InMemorySymmetricSecurityKey(keyForHmacSha256),
                    "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
                    "http://www.w3.org/2001/04/xmlenc#sha256"),
                sst.ValidFrom,
                sst.ValidTo
            );
            
            var validationParameters = new TokenValidationParameters()
            {
                AllowedAudience = "unc:abcd",
                ValidIssuer = "https://abcd.accesscontrol.windows.net/",
                ValidateExpiration = true,
                ValidateNotBefore = true,
                ValidateIssuer = true,
                ValidateSignature = true,
                SigningToken = new BinarySecretSecurityToken(Convert.FromBase64String("+blahblahblahblahblah=")),
            };
       
            JWTSecurityTokenHandler jwtHandler = new JWTSecurityTokenHandler();
            var jwtOnWire = jwtHandler.WriteToken(jwt);
            var claimPrincipal = jwtHandler.ValidateToken(jwtOnWire, validationParameters);
            JWTSecurityToken parsedJwt = jwtHandler.ReadToken(jwtOnWire) as JWTSecurityToken;

            // Use parsedJwt.RawData in the Authorization Header.