# RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants

This section contains the generic Python implementation of RFC7523.

## Using JWTs as Authorization Grants

Changed in version v1.0.0: Please note that all not-implemented methods are changed.

JWT Profile for OAuth 2.0 Authorization Grants works in the same way with RFC6749 built-in grants. Which means it can be registered with `register_grant()`.

The base class is `JWTBearerGrant`, you need to implement the missing methods in order to use it. Here is an example:

    from authlib.jose import JsonWebKey
    from authlib.oauth2.rfc7523 import JWTBearerGrant as _JWTBearerGrant

    class JWTBearerGrant(_JWTBearerGrant):
        def resolve_issuer_client(self, issuer):
            # if using client_id as issuer
            return Client.objects.get(client_id=issuer)

        def resolve_client_key(self, client, headers, payload):
            # if client has `jwks` column
            key_set = JsonWebKey.import_key_set(client.jwks)

            return key_set.find_by_kid(headers['kid'])

        def authenticate_user(self, subject):
            # when assertion contains `sub` value, if this `sub` is email
            return User.objects.get(email=subject)

        def has_granted_permission(self, client, user):
            # check if the client has access to user's resource.
            # for instance, we have a table `UserGrant`, which user can add client
            # to this table to record that client has granted permission
            grant = UserGrant.objects.get(client_id=client.client_id, user_id=user.id)
            if grant:
              return grant.enabled
            return False

    # register grant to authorization server
    authorization_server.register_grant(JWTBearerGrant)

When creating a client, authorization server will generate several key pairs. The server itself can only keep the public keys, which will be used to decode assertion value.

For client implementation, check out:

1. `AssertionSession`.

2. `AssertionSession`.

3. `AsyncAssertionSession`.

## Using JWTs for Client Authentication

In RFC6749: The OAuth 2.0 Authorization Framework, Authlib provided three built-in client authentication methods, which are `none`, `client_secret_post` and `client_secret_basic`. With the power of Assertion Framework, we can add more client authentication methods. In this section, Authlib provides two more options: `client_secret_jwt` and `private_key_jwt`. RFC7523 itself doesn’t define any names, these two names are defined by OpenID Connect in ClientAuthentication.

The `AuthorizationServer` has provided a method `register_client_auth_method()` to add more client authentication methods.

In Authlib, `client_secret_jwt` and `private_key_jwt` share the same API, using `JWTBearerClientAssertion` to create a new client authentication:

    class JWTClientAuth(JWTBearerClientAssertion):
        def validate_jti(self, claims, jti):
            # validate_jti is required by OpenID Connect
            # but it is optional by RFC7523
            # use cache to validate jti value
            key = 'jti:{}-{}'.format(claims['sub'], jti)
            if cache.get(key):
                return False
            cache.set(key, 1, timeout=3600)
            return True

        def resolve_client_public_key(self, client, headers):
            if headers['alg'] == 'HS256':
                return client.client_secret
            if headers['alg'] == 'RS256':
                return client.public_key
            # you may support other ``alg`` value

    authorization_server.register_client_auth_method(
        JWTClientAuth.CLIENT_AUTH_METHOD,
        JWTClientAuth('https://example.com/oauth/token')
    )

The value `https://example.com/oauth/token` is your authorization server’s token endpoint, which is used as `aud` value in JWT.

Now we have added this client auth method to authorization server, but no grant types support this authentication method, you need to add it to the supported grant types too, e.g. we want to support this in authorization code grant:

    from authlib.oauth2.rfc6749 import grants

    class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
        TOKEN_ENDPOINT_AUTH_METHODS = [
            'client_secret_basic',
            JWTClientAuth.CLIENT_AUTH_METHOD,
        ]
        # ...

You may noticed that the value of `CLIENT_AUTH_METHOD` is `client_assertion_jwt`. It is not `client_secret_jwt` or `private_key_jwt`, because they have the same logic. In the above implementation:

    def resolve_client_public_key(self, client, headers):
        alg = headers['alg']

If this `alg` is a MAC SHA like `HS256`, it is called `client_secret_jwt`, because the key used to sign a JWT is the client’s `client_secret` value. If this `alg` is `RS256` or something else, it is called `private_key_jwt`, because client will use its private key to sign the JWT. You can set a limitation in the implementation of `resolve_client_public_key` to accept only `HS256` alg, in this case, you can also alter `CLIENT_AUTH_METHOD = 'client_secret_jwt'`.

## Using JWTs Client Assertion in OAuth2Session

Authlib RFC7523 provides two more client authentication methods for OAuth 2 Session:

1. `client_secret_jwt`

2. `private_key_jwt`

Here is an example of how to register `client_secret_jwt` for `OAuth2Session`:

    from authlib.oauth2.rfc7523 import ClientSecretJWT
    from authlib.integrations.requests_client import OAuth2Session

    session = OAuth2Session(
        'your-client-id', 'your-client-secret',
        token_endpoint_auth_method='client_secret_jwt'
    )
    token_endpoint = 'https://example.com/oauth/token'
    session.register_client_auth_method(ClientSecretJWT(token_endpoint))
    session.fetch_token(token_endpoint)

How about `private_key_jwt`? It is the same as `client_secret_jwt`:

    from authlib.oauth2.rfc7523 import PrivateKeyJWT

    with open('your-private-key.pem', 'rb') as f:
        private_key = f.read()

    session = OAuth2Session(
        'your-client-id', private_key,
        token_endpoint_auth_method='private_key_jwt'  # NOTICE HERE
    )
    token_endpoint = 'https://example.com/oauth/token'
    session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
    session.fetch_token(token_endpoint)

## API Reference

class authlib.oauth2.rfc7523.JWTBearerGrant(request: OAuth2Request, server)
  GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'

    Designed for which “grant_type”

  CLAIMS_OPTIONS = {'aud': {'essential': True}, 'exp': {'essential': True}, 'iss': {'essential': True}}

    Options for verifying JWT payload claims. Developers MAY overwrite this constant to create a more strict options.

  process_assertion_claims(assertion)

    Extract JWT payload claims from request “assertion”, per Section 3.1.

    Parameters:

      assertion – assertion string value in the request

    Returns:

      JWTClaims

    Raise:

      InvalidGrantError

  validate_token_request()

    The client makes a request to the token endpoint by sending the following parameters using the “application/x-www-form-urlencoded” format per Section 2.1:

    grant_type

      REQUIRED. Value MUST be set to “urn:ietf:params:oauth:grant-type:jwt-bearer”.

    assertion

      REQUIRED. Value MUST contain a single JWT.

    scope

      OPTIONAL.

    The following example demonstrates an access token request with a JWT as an authorization grant:

        POST /token.oauth2 HTTP/1.1
        Host: as.example.com
        Content-Type: application/x-www-form-urlencoded

        grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
        &assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
        eyJpc3Mi[...omitted for brevity...].
        J9l-ZhwP[...omitted for brevity...]

  create_token_response()

    If valid and authorized, the authorization server issues an access token.

  resolve_issuer_client(issuer)

    Fetch client via “iss” in assertion claims. Developers MUST implement this method in subclass, e.g.:

        def resolve_issuer_client(self, issuer):
            return Client.query_by_iss(issuer)

    Parameters:

      issuer – “iss” value in assertion

    Returns:

      Client instance

  resolve_client_key(client, headers, payload)

    Resolve client key to decode assertion data. Developers MUST implement this method in subclass. For instance, there is a “jwks” column on client table, e.g.:

        def resolve_client_key(self, client, headers, payload):
            # from authlib.jose import JsonWebKey

            key_set = JsonWebKey.import_key_set(client.jwks)
            return key_set.find_by_kid(headers["kid"])

    Parameters:

      - client – instance of OAuth client model

      - headers – headers part of the JWT

      - payload – payload part of the JWT

    Returns:

      `authlib.jose.Key` instance

  authenticate_user(subject)

    Authenticate user with the given assertion claims. Developers MUST implement it in subclass, e.g.:

        def authenticate_user(self, subject):
            return User.get_by_sub(subject)

    Parameters:

      subject – “sub” value in claims

    Returns:

      User instance

  has_granted_permission(client, user)

    Check if the client has permission to access the given user’s resource. Developers MUST implement it in subclass, e.g.:

        def has_granted_permission(self, client, user):
            permission = ClientUserGrant.query(client=client, user=user)
            return permission.granted

    Parameters:

      - client – instance of OAuth client model

      - user – instance of User model

    Returns:

      bool

class authlib.oauth2.rfc7523.JWTBearerClientAssertion(token_url, validate_jti=True, leeway=60)

  Implementation of Using JWTs for Client Authentication, which is defined by RFC7523.

  CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

    Value of `client_assertion_type` of JWTs

  CLIENT_AUTH_METHOD = 'client_assertion_jwt'

    Name of the client authentication method

  create_claims_options()

    Create a claims_options for verify JWT payload claims. Developers MAY overwrite this method to create a more strict options.

  process_assertion_claims(assertion, resolve_key)

    Extract JWT payload claims from request “assertion”, per Section 3.1.

    Parameters:

      - assertion – assertion string value in the request

      - resolve_key – function to resolve the sign key

    Returns:

      JWTClaims

    Raise:

      InvalidClientError

  validate_jti(claims, jti)

    Validate if the given `jti` value is used before. Developers MUST implement this method:

        def validate_jti(self, claims, jti):
            key = "jti:{}-{}".format(claims["sub"], jti)
            if redis.get(key):
                return False
            redis.set(key, 1, ex=3600)
            return True

  resolve_client_public_key(client, headers)

    Resolve the client public key for verifying the JWT signature. A client may have many public keys, in this case, we can retrieve it via `kid` value in headers. Developers MUST implement this method:

        def resolve_client_public_key(self, client, headers):
            return client.public_key

class authlib.oauth2.rfc7523.ClientSecretJWT(token_endpoint=None, claims=None, headers=None, alg=None)

  Authentication method for OAuth 2.0 Client. This authentication method is called `client_secret_jwt`, which is using `client_id` and `client_secret` constructed with JWT to identify a client.

  Here is an example of use `client_secret_jwt` with Requests Session:

      from authlib.integrations.requests_client import OAuth2Session

      token_endpoint = "https://example.com/oauth/token"
      session = OAuth2Session(
          "your-client-id",
          "your-client-secret",
          token_endpoint_auth_method="client_secret_jwt",
      )
      session.register_client_auth_method(ClientSecretJWT(token_endpoint))
      session.fetch_token(token_endpoint)

  Parameters:

    - token_endpoint – A string URL of the token endpoint

    - claims – Extra JWT claims

    - headers – Extra JWT headers

    - alg – `alg` value, default is HS256

class authlib.oauth2.rfc7523.PrivateKeyJWT(token_endpoint=None, claims=None, headers=None, alg=None)

  Authentication method for OAuth 2.0 Client. This authentication method is called `private_key_jwt`, which is using `client_id` and `private_key` constructed with JWT to identify a client.

  Here is an example of use `private_key_jwt` with Requests Session:

      from authlib.integrations.requests_client import OAuth2Session

      token_endpoint = "https://example.com/oauth/token"
      session = OAuth2Session(
          "your-client-id",
          "your-client-private-key",
          token_endpoint_auth_method="private_key_jwt",
      )
      session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
      session.fetch_token(token_endpoint)

  Parameters:

    - token_endpoint – A string URL of the token endpoint

    - claims – Extra JWT claims

    - headers – Extra JWT headers

    - alg – `alg` value, default is RS256