first commit

This commit is contained in:
linkitapps
2026-09-14 23:22:10 +09:00
commit 9f9c8fb69b
4207 changed files with 1333187 additions and 0 deletions
@@ -0,0 +1,31 @@
"""authlib.oauth2.rfc7523.
~~~~~~~~~~~~~~~~~~~~~~
This module represents a direct implementation of
JSON Web Token (JWT) Profile for OAuth 2.0 Client
Authentication and Authorization Grants.
https://tools.ietf.org/html/rfc7523
"""
from .assertion import client_secret_jwt_sign
from .assertion import private_key_jwt_sign
from .auth import ClientSecretJWT
from .auth import PrivateKeyJWT
from .client import JWTBearerClientAssertion
from .jwt_bearer import JWTBearerGrant
from .token import JWTBearerTokenGenerator
from .validator import JWTBearerToken
from .validator import JWTBearerTokenValidator
__all__ = [
"JWTBearerGrant",
"JWTBearerClientAssertion",
"client_secret_jwt_sign",
"private_key_jwt_sign",
"ClientSecretJWT",
"PrivateKeyJWT",
"JWTBearerToken",
"JWTBearerTokenGenerator",
"JWTBearerTokenValidator",
]
@@ -0,0 +1,90 @@
import time
from joserfc import jwt
from authlib._joserfc_helpers import import_any_key
from authlib.common.security import generate_token
def sign_jwt_bearer_assertion(
key,
issuer,
audience,
subject=None,
issued_at=None,
expires_at=None,
claims=None,
header=None,
**kwargs,
):
if header is None:
header = {}
alg = kwargs.pop("alg", None)
if alg:
header["alg"] = alg
if "alg" not in header:
raise ValueError("Missing 'alg' in header")
payload = {"iss": issuer, "aud": audience}
# subject is not required in Google service
if subject:
payload["sub"] = subject
if not issued_at:
issued_at = int(time.time())
expires_in = kwargs.pop("expires_in", 3600)
if expires_at is None:
expires_at = issued_at + expires_in
payload["iat"] = issued_at
payload["exp"] = expires_at
if claims is None:
claims = {}
# jti uniquely identifies the JWT and prevents replay attacks (RFC 7523 §3)
if "jti" not in claims:
claims["jti"] = generate_token(36)
if claims:
payload.update(claims)
return jwt.encode(header, payload, import_any_key(key), algorithms=[header["alg"]])
def client_secret_jwt_sign(
client_secret, client_id, token_endpoint, alg="HS256", claims=None, **kwargs
):
return _sign(client_secret, client_id, token_endpoint, alg, claims, **kwargs)
def private_key_jwt_sign(
private_key, client_id, token_endpoint, alg="RS256", claims=None, **kwargs
):
return _sign(private_key, client_id, token_endpoint, alg, claims, **kwargs)
def _sign(key, client_id, token_endpoint, alg, claims=None, **kwargs):
# REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client.
issuer = client_id
# REQUIRED. Subject. This MUST contain the client_id of the OAuth Client.
subject = client_id
# The Audience SHOULD be the URL of the Authorization Server's Token Endpoint.
audience = token_endpoint
# jti is required
if claims is None:
claims = {}
if "jti" not in claims:
claims["jti"] = generate_token(36)
return sign_jwt_bearer_assertion(
key=key,
issuer=issuer,
audience=audience,
subject=subject,
claims=claims,
alg=alg,
**kwargs,
)
@@ -0,0 +1,116 @@
from joserfc.jwk import OctKey
from joserfc.jwk import RSAKey
from joserfc.jwk import ECKey
from joserfc.jwk import OKPKey
from authlib.common.urls import add_params_to_qs
from .assertion import client_secret_jwt_sign
from .assertion import private_key_jwt_sign
from .client import ASSERTION_TYPE
class ClientSecretJWT:
"""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)
:param token_endpoint: A string URL of the token endpoint
:param claims: Extra JWT claims
:param headers: Extra JWT headers
:param alg: ``alg`` value, default is HS256
"""
name = "client_secret_jwt"
alg = "HS256"
def __init__(self, token_endpoint=None, claims=None, headers=None, alg=None):
self.token_endpoint = token_endpoint
self.claims = claims
self.headers = headers
if alg is not None:
self.alg = alg
def sign(self, auth, token_endpoint):
if isinstance(auth.client_secret, OctKey):
key = auth.client_secret
else:
key = OctKey.import_key(auth.client_secret)
return client_secret_jwt_sign(
key,
client_id=auth.client_id,
token_endpoint=token_endpoint,
claims=self.claims,
header=self.headers,
alg=self.alg,
)
def __call__(self, auth, method, uri, headers, body):
token_endpoint = self.token_endpoint
if not token_endpoint:
token_endpoint = uri
client_assertion = self.sign(auth, token_endpoint)
body = add_params_to_qs(
body or "",
[
("client_assertion_type", ASSERTION_TYPE),
("client_assertion", client_assertion),
],
)
return uri, headers, body
class PrivateKeyJWT(ClientSecretJWT):
"""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)
:param token_endpoint: A string URL of the token endpoint
:param claims: Extra JWT claims
:param headers: Extra JWT headers
:param alg: ``alg`` value, default is RS256
"""
name = "private_key_jwt"
alg = "RS256"
def sign(self, auth, token_endpoint):
if isinstance(auth.client_secret, (RSAKey, ECKey, OKPKey)):
key = auth.client_secret
else:
key = RSAKey.import_key(auth.client_secret)
return private_key_jwt_sign(
key,
client_id=auth.client_id,
token_endpoint=token_endpoint,
claims=self.claims,
header=self.headers,
alg=self.alg,
)
@@ -0,0 +1,187 @@
from __future__ import annotations
import logging
from joserfc import jwk
from joserfc import jws
from joserfc import jwt
from joserfc.errors import JoseError
from joserfc.util import to_bytes
from authlib._joserfc_helpers import import_any_key
from authlib.common.encoding import json_loads
from authlib.deprecate import deprecate
from ..rfc6749 import InvalidClientError
ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
log = logging.getLogger(__name__)
class JWTBearerClientAssertion:
"""Implementation of Using JWTs for Client Authentication, which is
defined by RFC7523.
"""
#: Value of ``client_assertion_type`` of JWTs
CLIENT_ASSERTION_TYPE = ASSERTION_TYPE
#: Name of the client authentication method
CLIENT_AUTH_METHOD = "client_assertion_jwt"
def __init__(self, token_url=None, validate_jti=True, leeway=60):
if token_url is not None: # pragma: no cover
deprecate(
"'token_url' is deprecated. Override 'get_audiences' instead.",
version="1.8",
)
self.token_url = token_url
self._validate_jti = validate_jti
# A small allowance of time, typically no more than a few minutes,
# to account for clock skew. The default is 60 seconds.
self.leeway = leeway
def __call__(self, query_client, request):
data = request.form
assertion_type = data.get("client_assertion_type")
assertion = data.get("client_assertion")
if assertion_type == ASSERTION_TYPE and assertion:
headers, claims = self.extract_assertion(assertion)
# "sub" is read before the signature and the claims are verified,
# so it cannot be assumed to be present nor to be a string.
if "sub" not in claims:
raise InvalidClientError(description="Missing claim: 'sub'")
client_id = claims["sub"]
if not isinstance(client_id, str):
raise InvalidClientError(description="Invalid claim: 'sub'")
client = query_client(client_id)
if not client:
raise InvalidClientError(
description="The client does not exist on this server."
)
try:
key = import_any_key(self.resolve_client_public_key(client))
except TypeError: # pragma: no cover
key = import_any_key(self.resolve_client_public_key(client, headers))
deprecate(
"resolve_client_public_key takes only 'client' parameter.",
version="1.8",
)
request.client = client
self.process_assertion_claims(assertion, key)
return self.authenticate_client(request.client)
log.debug("Authenticate via %r failed", self.CLIENT_AUTH_METHOD)
def verify_claims(self, claims: jwt.Claims):
# iss and sub MUST be the client_id
options = {
"iss": {"essential": True},
"sub": {"essential": True},
"aud": {"essential": True, "values": self.get_audiences()},
"exp": {"essential": True},
}
claims_requests = jwt.JWTClaimsRegistry(leeway=self.leeway, **options)
try:
claims_requests.validate(claims)
except JoseError as e:
log.debug("Assertion Error: %r", e)
raise InvalidClientError(description=e.description) from e
if claims["sub"] != claims["iss"]:
raise InvalidClientError(description="Issuer and Subject MUST match.")
if self._validate_jti:
if "jti" not in claims:
raise InvalidClientError(description="Missing JWT ID.")
if not self.validate_jti(claims, claims["jti"]):
raise InvalidClientError(description="JWT ID is used before.")
def get_audiences(self):
"""Return a list of valid audience identifiers for this authorization
server. Per RFC 7523 Section 3, the audience identifies the
authorization server as an intended audience.
Developers MUST implement this method::
def get_audiences(self):
return ["https://example.com/oauth/token", "https://example.com"]
:return: list of valid audience strings
"""
if self.token_url is not None: # pragma: no cover
return [self.token_url]
raise NotImplementedError() # pragma: no cover
def process_assertion_claims(self, assertion, resolve_key):
"""Extract JWT payload claims from request "assertion", per
`Section 3.1`_.
:param assertion: assertion string value in the request
:param resolve_key: function to resolve the sign key
:return: JWTClaims
:raise: InvalidClientError
.. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
"""
try:
token = jwt.decode(assertion, resolve_key)
except JoseError as e:
log.debug("Assertion Error: %r", e)
raise InvalidClientError(description=e.description) from e
self.verify_claims(token.claims)
return token.claims
def authenticate_client(self, client):
if client.check_endpoint_auth_method(self.CLIENT_AUTH_METHOD, "token"):
return client
raise InvalidClientError(
description=f"The client cannot authenticate with method: {self.CLIENT_AUTH_METHOD}"
)
def extract_assertion(self, assertion: str):
try:
obj = jws.extract_compact(to_bytes(assertion))
except JoseError as e:
log.debug("Assertion Error: %r", e)
raise InvalidClientError(description="Invalid JWT assertion.") from e
try:
claims = json_loads(obj.payload)
except ValueError:
raise InvalidClientError(description="Invalid JWT payload.") from None
if not isinstance(claims, dict):
raise InvalidClientError(description="Invalid JWT payload.")
return obj.headers(), claims
def validate_jti(self, 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
"""
raise NotImplementedError()
def resolve_client_public_key(self, client) -> jwk.Key | jwk.KeySet:
"""Resolve the client public key for verifying the JWT signature.
Developers MUST implement this method::
from joserfc.jwk import KeySet
def resolve_client_public_key(self, client):
return KeySet.import_key_set(client.public_jwks)
"""
raise NotImplementedError()
@@ -0,0 +1,285 @@
import logging
from joserfc import jwk
from joserfc import jws
from joserfc import jwt
from joserfc.errors import JoseError
from joserfc.util import to_bytes
from authlib._joserfc_helpers import import_any_key
from authlib.common.encoding import json_loads
from authlib.deprecate import deprecate
from ..rfc6749 import BaseGrant
from ..rfc6749 import InvalidClientError
from ..rfc6749 import InvalidGrantError
from ..rfc6749 import InvalidRequestError
from ..rfc6749 import TokenEndpointMixin
from ..rfc6749 import UnauthorizedClientError
from .assertion import sign_jwt_bearer_assertion
log = logging.getLogger(__name__)
JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
class JWTBearerGrant(BaseGrant, TokenEndpointMixin):
GRANT_TYPE = JWT_BEARER_GRANT_TYPE
#: Options for verifying JWT payload claims. Developers MAY
#: overwrite this constant to create a more strict options.
CLAIMS_OPTIONS = {
"iss": {"essential": True},
"aud": {"essential": True},
"exp": {"essential": True},
}
# A small allowance of time, typically no more than a few minutes,
# to account for clock skew. The default is 60 seconds.
LEEWAY = 60
@staticmethod
def sign(
key,
issuer,
audience,
subject=None,
issued_at=None,
expires_at=None,
claims=None,
**kwargs,
):
return sign_jwt_bearer_assertion(
key, issuer, audience, subject, issued_at, expires_at, claims, **kwargs
)
def verify_claims(self, claims: jwt.Claims):
options = dict(self.CLAIMS_OPTIONS)
audiences = self.get_audiences()
if audiences:
options["aud"] = {"essential": True, "values": audiences}
else:
deprecate(
"'get_audiences' must return a non-empty list. "
"Audience validation will become mandatory.",
version="1.8",
)
claims_requests = jwt.JWTClaimsRegistry(leeway=self.LEEWAY, **options)
try:
claims_requests.validate(claims)
except JoseError as e:
log.debug("Assertion Error: %r", e)
raise InvalidGrantError(description=e.description) from e
def process_assertion_claims(self, assertion):
"""Extract JWT payload claims from request "assertion", per
`Section 3.1`_.
:param assertion: assertion string value in the request
:return: JWTClaims
:raise: InvalidGrantError
.. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
"""
headers, claims = self.extract_assertion(assertion)
# "iss" is read before the signature and the claims are verified, so it
# cannot be assumed to be present nor to be a string.
if "iss" not in claims:
raise InvalidGrantError(description="Missing claim: 'iss'")
issuer = claims["iss"]
if not isinstance(issuer, str):
raise InvalidGrantError(description="Invalid claim: 'iss'")
client = self.resolve_issuer_client(issuer)
if not client:
raise InvalidGrantError(
description="The client does not exist on this server."
)
if hasattr(self, "resolve_client_key"): # pragma: no cover
key = import_any_key(self.resolve_client_key(client, headers, claims))
deprecate(
"Use resolve_client_public_key instead of resolve_client_key.",
version="1.8",
)
else:
key = import_any_key(self.resolve_client_public_key(client))
try:
token = jwt.decode(assertion, key)
except JoseError as e:
log.debug("Assertion Error: %r", e)
raise InvalidGrantError(description=e.description) from e
except ValueError as e:
log.debug("Assertion Error: %r", e)
raise InvalidGrantError("Invalid JWT assertion") from None
self.verify_claims(token.claims)
return token.claims
def extract_assertion(self, assertion: str):
try:
obj = jws.extract_compact(to_bytes(assertion))
except JoseError as e:
log.debug("Assertion Error: %r", e)
raise InvalidGrantError(description="Invalid JWT assertion.") from e
try:
claims = json_loads(obj.payload)
except ValueError:
raise InvalidGrantError(description="Invalid JWT payload.") from None
if not isinstance(claims, dict):
raise InvalidGrantError(description="Invalid JWT payload.")
return obj.headers(), claims
def validate_token_request(self):
"""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:
.. code-block:: http
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...]
.. _`Section 2.1`: https://tools.ietf.org/html/rfc7523#section-2.1
"""
assertion = self.request.form.get("assertion")
if not assertion:
raise InvalidRequestError("Missing 'assertion' in request")
claims = self.process_assertion_claims(assertion)
client = self.resolve_issuer_client(claims["iss"])
log.debug("Validate token request of %s", client)
if not client.check_grant_type(self.GRANT_TYPE):
raise UnauthorizedClientError(
f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
)
self.request.client = client
self.validate_requested_scope()
subject = claims.get("sub")
if subject:
user = self.authenticate_user(subject)
if not user:
raise InvalidGrantError(description="Invalid 'sub' value in assertion")
log.debug("Check client(%s) permission to User(%s)", client, user)
if not self.has_granted_permission(client, user):
raise InvalidClientError(
description="Client has no permission to access user data"
)
self.request.user = user
def create_token_response(self):
"""If valid and authorized, the authorization server issues an access
token.
"""
token = self.generate_token(
scope=self.request.payload.scope,
user=self.request.user,
include_refresh_token=False,
)
log.debug("Issue token %r to %r", token, self.request.client)
self.save_token(token)
return 200, token, self.TOKEN_RESPONSE_HEADER
def resolve_issuer_client(self, 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)
:param issuer: "iss" value in assertion
:return: Client instance, or ``None`` when no client matches the
issuer. In that case an ``invalid_grant`` error is returned.
"""
raise NotImplementedError()
def resolve_client_public_key(self, client) -> jwk.Key | jwk.KeySet:
"""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_public_key(self, client):
from joserfc import KeySet
key_set = KeySet.import_key_set(client.jwks)
return key_set
:param client: instance of OAuth client model
:return: OctKey, RSAKey, ECKey, OKPKey or KeySet instance
"""
raise NotImplementedError()
def authenticate_user(self, 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)
:param subject: "sub" value in claims
:return: User instance
"""
raise NotImplementedError()
def get_audiences(self):
"""Return a list of valid audience identifiers for this authorization
server. Per RFC 7523 Section 3:
The authorization server MUST reject any JWT that does not
contain its own identity as the intended audience.
Developers SHOULD implement this method to return the list of valid
audience values, typically including the token endpoint URL and/or
the issuer identifier. For example::
def get_audiences(self):
return ["https://example.com/oauth/token", "https://example.com"]
If this method returns an empty list, audience value validation is
skipped (only presence is checked).
:return: list of valid audience strings
"""
return []
def has_granted_permission(self, 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
:param client: instance of OAuth client model
:param user: instance of User model
:return: bool
"""
raise NotImplementedError()
@@ -0,0 +1,108 @@
import time
from joserfc import jwt
from authlib._joserfc_helpers import import_any_key
class JWTBearerTokenGenerator:
"""A JSON Web Token formatted bearer token generator for jwt-bearer grant type.
This token generator can be registered into authorization server::
authorization_server.register_token_generator(
"urn:ietf:params:oauth:grant-type:jwt-bearer",
JWTBearerTokenGenerator(private_rsa_key),
)
In this way, we can generate the token into JWT format. And we don't have to
save this token into database, since it will be short time valid. Consider to
rewrite ``JWTBearerGrant.save_token``::
class MyJWTBearerGrant(JWTBearerGrant):
def save_token(self, token):
pass
:param secret_key: private RSA key in bytes, JWK or JWK Set.
:param issuer: a string or URI of the issuer
:param alg: ``alg`` to use in JWT
"""
DEFAULT_EXPIRES_IN = 3600
def __init__(self, secret_key, issuer=None, alg="RS256"):
self.secret_key = import_any_key(secret_key)
self.issuer = issuer
self.alg = alg
@staticmethod
def get_allowed_scope(client, scope):
if scope:
scope = client.get_allowed_scope(scope)
return scope
@staticmethod
def get_sub_value(user):
"""Return user's ID as ``sub`` value in token payload. For instance::
@staticmethod
def get_sub_value(user):
return str(user.id)
"""
return str(user.get_user_id())
def get_token_data(self, grant_type, client, expires_in, user=None, scope=None):
scope = self.get_allowed_scope(client, scope)
issued_at = int(time.time())
data = {
"scope": scope,
"grant_type": grant_type,
"iat": issued_at,
"exp": issued_at + expires_in,
"client_id": client.get_client_id(),
}
if self.issuer:
data["iss"] = self.issuer
if user:
data["sub"] = self.get_sub_value(user)
return data
def generate(self, grant_type, client, user=None, scope=None, expires_in=None):
"""Generate a bearer token for OAuth 2.0 authorization token endpoint.
:param client: the client that making the request.
:param grant_type: current requested grant_type.
:param user: current authorized user.
:param expires_in: if provided, use this value as expires_in.
:param scope: current requested scope.
:return: Token dict
"""
if expires_in is None:
expires_in = self.DEFAULT_EXPIRES_IN
token_data = self.get_token_data(grant_type, client, expires_in, user, scope)
access_token = jwt.encode(
{"alg": self.alg},
claims=token_data,
key=self.secret_key,
algorithms=[self.alg],
)
token = {
"token_type": "Bearer",
"access_token": access_token,
"expires_in": expires_in,
}
if scope:
token["scope"] = scope
return token
def __call__(
self,
grant_type,
client,
user=None,
scope=None,
expires_in=None,
include_refresh_token=True,
):
# there is absolutely no refresh token in JWT format
return self.generate(grant_type, client, user, scope, expires_in)
@@ -0,0 +1,63 @@
import logging
import time
from joserfc import jwt
from joserfc.errors import JoseError
from authlib._joserfc_helpers import import_any_key
from ..rfc6749 import TokenMixin
from ..rfc6750 import BearerTokenValidator
logger = logging.getLogger(__name__)
class JWTBearerToken(TokenMixin, dict):
def check_client(self, client):
return self["client_id"] == client.get_client_id()
def get_scope(self):
return self.get("scope")
def get_expires_in(self):
return self["exp"] - self["iat"]
def is_expired(self):
return self["exp"] < time.time()
def is_revoked(self):
return False
class JWTBearerTokenValidator(BearerTokenValidator):
TOKEN_TYPE = "bearer"
token_cls = JWTBearerToken
def __init__(self, public_key, issuer=None, realm=None, leeway=60, **extra_attributes):
super().__init__(realm, **extra_attributes)
self.public_key = import_any_key(public_key)
self.leeway = leeway
claims_options = {
"exp": {"essential": True},
"client_id": {"essential": True},
"grant_type": {"essential": True},
}
if issuer:
claims_options["iss"] = {"essential": True, "value": issuer}
self.claims_options = claims_options
def authenticate_token(self, token_string: str):
try:
token = jwt.decode(token_string, self.public_key)
except JoseError as error:
logger.debug("Authenticate token failed. %r", error)
return None
claims_requests = jwt.JWTClaimsRegistry(leeway=self.leeway, **self.claims_options)
try:
claims_requests.validate(token.claims)
except JoseError as error:
logger.debug("Authenticate token failed. %r", error)
return None
return JWTBearerToken(token.claims)