3

Im using the jwcrypto library to create a signed JWT. The requirement is to produce a JWT signed by the private component of an RSA key. I took the steps below

Create JWK key pair

from jwcrypto import jwk,jwt
key = jwk.JWK.generate(
            kty='RSA', 
            size=2048, 
            kid='test',
            use='sig',
            e='AQAB',
            alg='RS256'
        )

private_key = key.export_private()
public_key = key.export_public(as_dict=True)

I then sent out the public key to the server and created the signed JWT like this, probably doing it wrong:

from datetime import datetime as dt

jwt_header = {
    'alg':'RS256',
    'kid':'test',
}

jwt_claims = {
    'iss':'767676',
    'sub':'test',
    'aud':'https://example.com',
    'token.aud': 'https://example.com',
    'iat':int(dt.now().timestamp()),
    'exp':int(dt.now().timestamp())+600
    
}

jwt_token = jwt.JWT(
        header = jwt_header,
        claims = jwt_claims,
    )
jwt_token.make_signed_token(key)
signed_jwt = jwt_token.serialize()

Sending JWT to Server:

headers = {
    'Accept-Encoding':'gzip,deflate',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Host': 'test.example.com',
    'Connection': 'Keep-Alive',
    'User-Agent': 'TestApp/1.0.0'
    }

params = {
    'grant_type':'urn:ietf:params:oauth:grant-type:jwt-bearer',
    'assertion':signed_jwt,
    'client_id':'123456'
}

r = requests.post("https:example.com",headers=headers,params=params)

auth_data = r.json()

When I pass the signed signed_jwt to server I get an error 'Invalid Grant Type. Only jwt-bearer supported.

How can I get this working?

Also happy for an answer that uses a different library

West
  • 2,350
  • 5
  • 31
  • 67
  • Please post how you sent the token to the server? include request headers. – Tore Nestenius Jan 12 '22 at 08:13
  • @ToreNestenius Sure, I've done an update – West Jan 12 '22 at 08:33
  • What kind of server are you posting it to and what do you want to achieve? An API will not accept a JWT token signed with your own key. the API needs it to be signed by a key it trusts. – Tore Nestenius Jan 12 '22 at 08:52
  • @ToreNestenius Not sure what kind of server, its just an endpoint that should authenticate me using the public key I sent in the first step, then it returns an access token for accessing another service. Im just not sure if I signed the JWT correctly with the private key? – West Jan 12 '22 at 08:56
  • in a normal usecase, you authenticate with an authorization server, and it issues the token for you, and then you send the token to the API and the API then asks the auth server for the public key to validate the token signature. Its very rare that anyone would accept a token signed by your self. – Tore Nestenius Jan 12 '22 at 08:59
  • @ToreNestenius I see. In this case they specifically require it to be signed by the private key I generated then they verify using the public. – West Jan 12 '22 at 09:15

1 Answers1

0

It looks like it is more of a problem with how you interact with the server (example.com above). You should check its API documentation.

Normally the token is sent as an header like so:

headers = {
    # ...
    "Authorization": "Bearer {signed_jwt}"
}
Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
japs
  • 1,286
  • 13
  • 27
  • So is the way I signed the JWT correct? The docs say the jwt should be in the requests body – West Jan 12 '22 at 09:10
  • `The authentication service is a standard OAuth 2.0 token end-point, or authorisation service, which is configured to generate an OAuth2.0 JWT Bearer Token authorisation grant type. The authorisation service is an implementation of the OAuth Assertion Framework. In order for an app to be able to use the service the following specific parameter values must be provided in a service requests body: The value of the ‘grant_type’ parameter must be ‘urn:ietf:params:oauth:grant-type:jwt-bearer’. The value of ‘assertion’ must contain a single JWT signed by the software instance’s private key.` – West Jan 12 '22 at 09:10
  • This article also confirms the jwt is sent as a parameter https://ldapwiki.com/wiki/JSON%20Web%20Token%20%28JWT%29%20Profile%20for%20OAuth%202.0%20Client%20Authentication%20and%20Authorization%20Grants – West Jan 12 '22 at 09:16
  • If the target of the token is an API , it is up to the API to define how to pass the token to it. Typically, an API that works with tokens uses the authentication bearer header , as described here https://swagger.io/docs/specification/authentication/bearer-authentication/ but what do you want to do with the token? it seems you try to pass it back to the authorization server? if so, why? – Tore Nestenius Jan 12 '22 at 09:21
  • @ToreNestenius Sorry i wasnt clear on that. There's 2 steps, first I pass the signed jwt to a server that authenticates who i am,. Server then sends me some access token. Second step I then use this access token to access the actual API that I want to use. And yes that API actually defines how to pass the access token to it, and its through the headers as the answer here says. Im just stuck on the first step – West Jan 12 '22 at 09:31
  • My main issue is how to properly create a jwt token signed by a private key – West Jan 12 '22 at 09:34
  • ok, sorry was a bit confused. The signed TWK token should not just be the serialized token, it needs to also be Base64-url encoded, see https://stackoverflow.com/questions/56711129/why-do-you-use-base64-url-encoding-with-json-web-tokens – Tore Nestenius Jan 12 '22 at 09:38
  • do, try the token in https://jwt.io – Tore Nestenius Jan 12 '22 at 09:38
  • see https://ldapwiki.com/wiki/JSON%20Web%20Tokens that says "Then, this JSON is Base64Url encoded to form the first part of the JWT." – Tore Nestenius Jan 12 '22 at 09:45
  • @ToreNestenius Thanks. The token decodes fine in jwt.io and signature is verified. I believe the `jwcrypto` library already encodes the `header` and `claims` using base64 library `base64. urlsafe_b64encode(payload)`. I updated my question before as it seems the function I used creates a JWS signed token not a JWT. Do you know any other way to get a signed JWT with this or other library? – West Jan 12 '22 at 10:14
  • A signed JWT is known as a JWS (JSON Web Signature). In fact, a JWT does not exist itself — either it has to be a JWS or a JWE (JSON Web Encryption). It is like an abstract class — the JWS and JWE are the concrete implementations. – Tore Nestenius Jan 12 '22 at 10:35
  • Honestly, I don't think the token is the problem, instead it might be a auth server config issue or you send the wrong parameters. Perhaps rename 'grant_type':'urn:ietf:params:oauth:grant-type:jwt-bearer', to just 'grant_type':'jwt-bearer' ? – Tore Nestenius Jan 12 '22 at 10:36
  • @ToreNestenius Yes I believe issue is on the server. I tried changing the grant type with no luck. Also tried using the `pyjwt` library but hasn't worked. It seems `urn:ietf:params:oauth:grant-type:jwt-bearer.` is the correct grant type as their docs say so and I saw the same being used here https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow. Have emailed the server people will hear what they say. Thanks for the assistance – West Jan 12 '22 at 14:18
  • Don't you also have to add the scope parameters? – Tore Nestenius Jan 12 '22 at 14:21
  • @ToreNestenius No it isnt required in this step – West Jan 12 '22 at 15:04