RFC9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens

This section contains the generic implementation of RFC9068. JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens allows developpers to generate JWT access tokens.

Using JWT instead of plain text for access tokens result in different possibilities:

  • User information can be filled in the JWT claims, similar to the OpenID Connect 1.0 id_token, possibly making the economy of requests to the userinfo_endpoint.

  • Resource servers do not need to reach the authorization server RFC7662: OAuth 2.0 Token Introspection endpoint to verify each incoming tokens, as the JWT signature is a proof of its validity. This brings the economy of one network request at each resource access.

  • Consequently, the authorization server do not need to store access tokens in a database. If a resource server does not implement this spec and still need to reach the authorization server introspection endpoint to check the token validation, then the authorization server can simply validate the JWT without requesting its database.

  • If the authorization server do not store access tokens in a database, it won’t have the possibility to revoke the tokens. The produced access tokens will be valid until the timestamp defined in its exp claim is reached.

This specification is just about access tokens. Other kinds of tokens like refresh tokens are not covered.

RFC9068 define a few optional JWT claims inspired from RFC7643 that can can be used to determine if the token bearer is authorized to access a resource: groups, roles and entitlements.

This module brings tools to:

API Reference

class authlib.oauth2.rfc9068.JWTBearerTokenGenerator(issuer, alg='RS256', refresh_token_generator=None, expires_generator=None)

A JWT formatted access token generator.

Parameters:
  • issuer – The issuer identifier. Will appear in the JWT iss claim.

  • **kwargs – Other parameters are inherited from BearerTokenGenerator.

This token generator can be registered into the authorization server:

class MyJWTBearerTokenGenerator(JWTBearerTokenGenerator):
    def get_jwks(self):
        ...

    def get_extra_claims(self, client, grant_type, user, scope):
        ...

authorization_server.register_token_generator(
    'default',
    MyJWTBearerTokenGenerator(issuer='https://authorization-server.example.org'),
)
get_jwks()

Return the JWKs that will be used to sign the JWT access token. Developers MUST re-implement this method:

def get_jwks(self):
    return load_jwks("jwks.json")
get_extra_claims(client, grant_type, user, scope)

Return extra claims to add in the JWT access token. Developers MAY re-implement this method to add identity claims like the ones in OpenID Connect 1.0 ID Token, or any other arbitrary claims:

def get_extra_claims(self, client, grant_type, user, scope):
    return generate_user_info(user, scope)
get_audiences(client, user, scope) str | List[str]

Return the audience for the token. By default this simply returns the client ID. Developpers MAY re-implement this method to add extra audiences:

def get_audiences(self, client, user, scope):
    return [
        client.get_client_id(),
        resource_server.get_id(),
    ]
get_acr(user) str | None

Authentication Context Class Reference. Returns a user-defined case sensitive string indicating the class of authentication the used performed. Token audience may refuse to give access to some resources if some ACR criterias are not met. OpenID Connect 1.0 defines one special value: 0 means that the user authentication did not respect ISO29115 level 1, and will be refused monetary operations. Developers MAY re-implement this method:

def get_acr(self, user):
    if user.insecure_session():
        return '0'
    return 'urn:mace:incommon:iap:silver'
get_auth_time(user) int | None

User authentication time. Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. Developers MAY re-implement this method:

def get_auth_time(self, user):
    return datetime.timestamp(user.get_auth_time())
get_amr(user) List[str] | None

Authentication Methods References. Defined by OpenID Connect 1.0 as an option list of user-defined case-sensitive strings indication which authentication methods have been used to authenticate the user. Developers MAY re-implement this method:

def get_amr(self, user):
    return ['2FA'] if user.has_2fa_enabled() else []
get_jti(client, grant_type, user, scope) str

JWT ID. Create an unique identifier for the token. Developers MAY re-implement this method:

def get_jti(self, client, grant_type, user scope):
    return generate_random_string(16)
class authlib.oauth2.rfc9068.JWTBearerTokenValidator(issuer, resource_server, *args, **kwargs)

JWTBearerTokenValidator can protect your resource server endpoints.

Parameters:
  • issuer – The issuer from which tokens will be accepted.

  • resource_server – An identifier for the current resource server, which must appear in the JWT aud claim.

Developers needs to implement the missing methods:

class MyJWTBearerTokenValidator(JWTBearerTokenValidator):
    def get_jwks(self):
        ...

require_oauth = ResourceProtector()
require_oauth.register_token_validator(
    MyJWTBearerTokenValidator(
        issuer='https://authorization-server.example.org',
        resource_server='https://resource-server.example.org',
    )
)

You can then protect resources depending on the JWT scope, groups, roles or entitlements claims:

@require_oauth(
    scope='profile',
    groups='admins',
    roles='student',
    entitlements='captain',
)
def resource_endpoint():
    ...
get_jwks()

Return the JWKs that will be used to check the JWT access token signature. Developers MUST re-implement this method. Typically the JWKs are statically stored in the resource server configuration, or dynamically downloaded and cached using RFC8414: OAuth 2.0 Authorization Server Metadata:

def get_jwks(self):
    if 'jwks' in cache:
        return cache.get('jwks')

    server_metadata = get_server_metadata(self.issuer)
    jwks_uri = server_metadata.get('jwks_uri')
    cache['jwks'] = requests.get(jwks_uri).json()
    return cache['jwks']
class authlib.oauth2.rfc9068.JWTIntrospectionEndpoint(issuer, server=None, *args, **kwargs)

JWTIntrospectionEndpoint inherits from RFC7662: OAuth 2.0 Token Introspection IntrospectionEndpoint and implements the machinery to automatically process the JWT access tokens.

Parameters:
  • issuer – The issuer identifier for which tokens will be introspected.

  • **kwargs – Other parameters are inherited from IntrospectionEndpoint.

class MyJWTAccessTokenIntrospectionEndpoint(JWTRevocationEndpoint):
    def get_jwks(self):
        ...

    def get_username(self, user_id):
        ...

authorization_server.register_endpoint(
    MyJWTAccessTokenIntrospectionEndpoint(
        issuer="https://authorization-server.example.org",
    )
)
authorization_server.register_endpoint(MyRefreshTokenIntrospectionEndpoint)
ENDPOINT_NAME = 'introspection'

Endpoint name to be registered

get_jwks()

Return the JWKs that will be used to check the JWT access token signature. Developers MUST re-implement this method:

def get_jwks(self):
    return load_jwks("jwks.json")
get_username(user_id: str) str

Returns an username from a user ID. Developers MAY re-implement this method:

def get_username(self, user_id):
    return User.get(id=user_id).username
class authlib.oauth2.rfc9068.JWTRevocationEndpoint(issuer, server=None, *args, **kwargs)

JWTRevocationEndpoint inherits from RFC7009 RevocationEndpoint.

The JWT access tokens cannot be revoked. If the submitted token is a JWT access token, then revocation returns a invalid_token_error.

Parameters:
  • issuer – The issuer identifier.

  • **kwargs – Other parameters are inherited from RevocationEndpoint.

Plain text access tokens and other kind of tokens such as refresh_tokens will be ignored by this endpoint and passed to the next revocation endpoint:

class MyJWTAccessTokenRevocationEndpoint(JWTRevocationEndpoint):
    def get_jwks(self):
        ...

authorization_server.register_endpoint(
    MyJWTAccessTokenRevocationEndpoint(
        issuer="https://authorization-server.example.org",
    )
)
authorization_server.register_endpoint(MyRefreshTokenRevocationEndpoint)
get_jwks()

Return the JWKs that will be used to check the JWT access token signature. Developers MUST re-implement this method:

def get_jwks(self):
    return load_jwks("jwks.json")