asp.netasp.net-web-apiasp.net-web-api2windows-live

How to verify a Windows Live Connect JWT authentication_token?


I'm using the Windows Live Connect javascript SDK to log in a user on a web page. In order to pass the identity assertion to my server, this SDK provides a signed JWT token as WL.getSession().authentication_token. It appears to be a standard JWT, but I am unable to verify the signature.

What secret do I use? I have tried the client secret for my application from the Microsoft Account Dev Center, but this fails signature verification in both my JWT libraries and online JWT checkers (eg jwt.io).

Documentation for this token is haphazard. The primary documentation appears to be this. However, the code sample has been dropped in a migration and needs to be pulled out of github history; in any case, it merely says use the "application secret" without mentioning its origin.

This blog entry says I should go to http://appdev.microsoft.com/StorePortals, however, my app is not part of the windows store; it's a standard developer center application (https://account.live.com/developers/applications/index).

I have found an official microsoft video describing how to decode the token (see slide 15, or watch the video at 29:35). Also ambiguous as to where the secret comes from. Even worse, it references a SDK method that does not show up in the present SDK (LiveAuthClient.GetUserId()).

I'm baffled. Yes, I know I can take the access_token and fetch the user id from the profile endpoint, but I need to avoid this extra API roundtrip. The JWT authentication_token is clearly present for exactly this purpose - how can I verify the content?


Solution

  • You need the "JWTSig" as in this C# sample:

        public static byte[] EncodeSigningToken(string token)
        {
            try
            {
    
                var sha256 = new SHA256Managed();
                var secretBytes = StrToByteArray(token + "JWTSig");
    
                var signingKey = sha256.ComputeHash(secretBytes);
    
                return signingKey;
            }
            catch (Exception)
            {
                return null;
            }
        }
    

    Or this:

        private void ValidateSignature(string key)
        {
            // Derive signing key, Signing key = SHA256(secret + "JWTSig")
            byte[] bytes = UTF8Encoder.GetBytes(key + "JWTSig");
            byte[] signingKey = SHA256Provider.ComputeHash(bytes);
    
            // To Validate:
            // 
            // 1. Take the bytes of the UTF-8 representation of the JWT Claim
            //  Segment and calculate an HMAC SHA-256 MAC on them using the
            //  shared key.
            //
            // 2. Base64url encode the previously generated HMAC as defined in this
            //  document.
            //
            // 3. If the JWT Crypto Segment and the previously calculated value
            //  exactly match in a character by character, case sensitive
            //  comparison, then one has confirmation that the key was used to
            //  generate the HMAC on the JWT and that the contents of the JWT
            //  Claim Segment have not be tampered with.
            //
            // 4. If the validation fails, the token MUST be rejected.
    
            // UFT-8 representation of the JWT envelope.claim segment
            byte[] input = UTF8Encoder.GetBytes(this.envelopeTokenSegment + "." + this.claimsTokenSegment);
    
            // calculate an HMAC SHA-256 MAC
            using (HMACSHA256 hashProvider = new HMACSHA256(signingKey))
            {
                byte[] myHashValue = hashProvider.ComputeHash(input);
    
                // Base64 url encode the hash
                string base64urlEncodedHash = this.Base64UrlEncode(myHashValue);
    
                // Now compare the two has values
                if (base64urlEncodedHash != this.Signature)
                {
                    throw new Exception("Signature does not match.");
                }
            }
        }