Search code examples
pythonc#jwtbase64pyjwt

Cannot decode token in python created in ASP.NET using secret


Secret is created in .Net using below code

var key = new byte[32];
RNGCryptoServiceProvider.Create().GetBytes(key);
var base64Secret = TextEncodings.Base64Url.Encode(key)
Audience newAudience = new Audience { ClientId = clientId, Base64Secret = base64Secret, Name = name };

Token is created in .Net using below code

string symmetricKeyAsBase64 = audience.Base64Secret;    
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);

Above code successfully created a token which needs to be decoded in python:

Secret is XYZ which is generated and stored in the database. Secret is encoded using TextEncodings.Base64Url.Encode before storing into the database. I tried to decode the secret key in python by adding a "="

base64.urlsafe_b64decode("XYZ=")

I also tried by adding double equal "==" using below method

base64.b64decode("XYZ==")

Fianlly I tried above two methods to decode the secret and use it in jwt.decode()

jwt.decode(token, secret, algorithms=['HS256'])

None of the things worked.

Token looks like

HEADER:ALGORITHM & TOKEN TYPE

{
  "typ": "JWT",
  "alg": "HS256"
}
PAYLOAD:DATA

{
  "unique_name": "devuser",
  "sub": "devuser",
  "role": [
    "Manager",
    "Supervisor"
  ],
  "iss": "https://xxxxxxx.azurewebsites.net",
  "aud": "6A00574AE5514C1C90D2D5332FEF78F9",
  "exp": 1596636265,
  "nbf": 1596634465
}

**VERIFY SIGNATURE**
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
)

Solution

  • First the secret needs padding. Altough Base64Url encoding usually does not require padding, the Python Base64 decoder wants it. So the secret is:

    XYZ=
    

    (padded to 44 characters).

    Without padding you would get:

    binascii.Error: Incorrect padding

    And then your token contains an audience claim and PyJWT has to verify the audience.

    Quoting the above linked documentation:

    If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected.

    If you don't pass a valid audience as a parameter to decode, you'll get an exception:

    InvalidAudienceError('Invalid audience')

    If you pass the valid audience, like in this example:

    import jwt
    import base64
    
    token = "abc"
    
    secret = base64.urlsafe_b64decode("XYZ=")
    allowed_audience = "6A00574AE5514C1C90D2D5332FEF78F9"
    
    decoded = jwt.decode(token, secret, audience = allowed_audience)
    print(decoded)
    

    it works fine.