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,167 @@
"""authlib.oauth2.rfc9068.token_validator.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implementation of Validating JWT Access Tokens per `Section 4`_.
.. _`Section 7`: https://www.rfc-editor.org/rfc/rfc9068.html#name-validating-jwt-access-token
"""
from joserfc import jwt
from joserfc.errors import JoseError
from authlib._joserfc_helpers import import_any_key
from authlib.oauth2.claims import ClaimsOption
from authlib.oauth2.rfc6750.errors import InsufficientScopeError
from authlib.oauth2.rfc6750.errors import InvalidTokenError
from authlib.oauth2.rfc6750.validator import BearerTokenValidator
from .claims import JWTAccessTokenClaims
class JWTBearerTokenValidator(BearerTokenValidator):
"""JWTBearerTokenValidator can protect your resource server endpoints.
:param issuer: The issuer from which tokens will be accepted.
:param 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(): ...
"""
def __init__(self, issuer, resource_server, *args, **kwargs):
self.issuer = issuer
self.resource_server = resource_server
super().__init__(*args, **kwargs)
def get_jwks(self):
"""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 :ref:`specs/rfc8414`::
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"]
"""
raise NotImplementedError()
def validate_iss(self, claims, iss: "str") -> bool:
# The issuer identifier for the authorization server (which is typically
# obtained during discovery) MUST exactly match the value of the 'iss'
# claim.
return iss == self.issuer
def authenticate_token(self, token_string):
""""""
# empty docstring avoids to display the irrelevant parent docstring
claims_options: dict[str, ClaimsOption] = {
"iss": {"essential": True, "validate": self.validate_iss},
"exp": {"essential": True},
"aud": {"essential": True, "value": self.resource_server},
"sub": {"essential": True},
"client_id": {"essential": True},
"iat": {"essential": True},
"jti": {"essential": True},
"auth_time": {"essential": False},
"acr": {"essential": False},
"amr": {"essential": False},
"scope": {"essential": False},
"groups": {"essential": False},
"roles": {"essential": False},
"entitlements": {"essential": False},
}
key = import_any_key(self.get_jwks())
# If the JWT access token is encrypted, decrypt it using the keys and algorithms
# that the resource server specified during registration. If encryption was
# negotiated with the authorization server at registration time and the incoming
# JWT access token is not encrypted, the resource server SHOULD reject it.
# The resource server MUST validate the signature of all incoming JWT access
# tokens according to [RFC7515] using the algorithm specified in the JWT 'alg'
# Header Parameter. The resource server MUST reject any JWT in which the value
# of 'alg' is 'none'. The resource server MUST use the keys provided by the
# authorization server.
try:
token = jwt.decode(token_string, key=key)
return JWTAccessTokenClaims(token.claims, token.header, claims_options)
except JoseError as exc:
raise InvalidTokenError(
realm=self.realm, extra_attributes=self.extra_attributes
) from exc
def validate_token(
self,
token: JWTAccessTokenClaims,
scopes,
request,
groups=None,
roles=None,
entitlements=None,
):
""""""
# empty docstring avoids to display the irrelevant parent docstring
try:
token.validate()
except JoseError as exc:
raise InvalidTokenError(
realm=self.realm, extra_attributes=self.extra_attributes
) from exc
# If an authorization request includes a scope parameter, the corresponding
# issued JWT access token SHOULD include a 'scope' claim as defined in Section
# 4.2 of [RFC8693]. All the individual scope strings in the 'scope' claim MUST
# have meaning for the resources indicated in the 'aud' claim. See Section 5 for
# more considerations about the relationship between scope strings and resources
# indicated by the 'aud' claim.
if self.scope_insufficient(token.get("scope", []), scopes):
raise InsufficientScopeError()
# Many authorization servers embed authorization attributes that go beyond the
# delegated scenarios described by [RFC7519] in the access tokens they issue.
# Typical examples include resource owner memberships in roles and groups that
# are relevant to the resource being accessed, entitlements assigned to the
# resource owner for the targeted resource that the authorization server knows
# about, and so on. An authorization server wanting to include such attributes
# in a JWT access token SHOULD use the 'groups', 'roles', and 'entitlements'
# attributes of the 'User' resource schema defined by Section 4.1.2 of
# [RFC7643]) as claim types.
if self.scope_insufficient(token.get("groups"), groups):
raise InvalidTokenError()
if self.scope_insufficient(token.get("roles"), roles):
raise InvalidTokenError()
if self.scope_insufficient(token.get("entitlements"), entitlements):
raise InvalidTokenError()