This section contains the generic Python implementation of RFC7523.
authlib.oauth2.rfc7523
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 <specs/rfc6749>
built-in grants. Which means it can be registered with ~authlib.oauth2.rfc6749.AuthorizationServer.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:
~authlib.integrations.requests_client.AssertionSession
.~authlib.integrations.httpx_client.AssertionSession
.~authlib.integrations.httpx_client.AsyncAssertionSession
.
In specs/rfc6749
, 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 ~authlib.oauth2.rfc6749.AuthorizationServer
has provided a method ~authlib.oauth2.rfc6749.AuthorizationServer.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'
.
Authlib RFC7523 provides two more client authentication methods for oauth_2_session
:
client_secret_jwt
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)
JWTBearerGrant
JWTBearerClientAssertion
ClientSecretJWT
PrivateKeyJWT