-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
jwt_backends: create backend mechanism and add authlib support
- Loading branch information
Showing
16 changed files
with
696 additions
and
397 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
from .jwt import * # noqa: F401, F403 | ||
from .jwt_backends import * # noqa: F401, F403 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
try: | ||
from .authlib_backend import AuthlibJWTBackend | ||
except ImportError: | ||
AuthlibJWTBackend = None | ||
|
||
try: | ||
from .python_jose_backend import PythonJoseJWTBackend | ||
except ImportError: | ||
PythonJoseJWTBackend = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from abc import ABCMeta, abstractmethod, abstractproperty | ||
from typing import Any, Dict, Optional, Self | ||
|
||
|
||
|
||
class AbstractJWTBackend(metaclass=ABCMeta): | ||
|
||
# simple "SingletonArgs" implementation to keep a JWTBackend per algorithm | ||
_instances = {} | ||
|
||
def __new__(cls, algorithm) -> Self: | ||
instance_key = (cls, algorithm) | ||
if instance_key not in cls._instances: | ||
cls._instances[instance_key] = super(AbstractJWTBackend, cls).__new__(cls) | ||
return cls._instances[instance_key] | ||
|
||
@abstractmethod | ||
def __init__(self, algorithm) -> None: | ||
pass | ||
|
||
@abstractproperty | ||
def default_algorithm(self) -> str: | ||
pass | ||
|
||
@abstractmethod | ||
def encode(self, to_encode, secret_key) -> str: | ||
pass | ||
|
||
@abstractmethod | ||
def decode(self, token, secret_key, auto_error) -> Optional[Dict[str, Any]]: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from fastapi import HTTPException | ||
from typing import Any, Dict, Optional | ||
from starlette.status import HTTP_401_UNAUTHORIZED | ||
|
||
from authlib.jose import JsonWebSignature, JsonWebToken | ||
from authlib.jose.errors import ( | ||
DecodeError, ExpiredTokenError, InvalidClaimError, InvalidTokenError | ||
) | ||
from .abstract_backend import AbstractJWTBackend | ||
|
||
|
||
class AuthlibJWTBackend(AbstractJWTBackend): | ||
|
||
def __init__(self, algorithm) -> None: | ||
self.algorithm = algorithm if algorithm is not None else self.default_algorithm | ||
# from https://github.com/lepture/authlib/blob/85f9ff/authlib/jose/__init__.py#L45 | ||
valid_algorithms = list(JsonWebSignature.ALGORITHMS_REGISTRY.keys()) | ||
assert ( | ||
self.algorithm in valid_algorithms | ||
), f"{self.algorithm} algorithm is not supported by authlib" | ||
self.jwt = JsonWebToken(algorithms=[self.algorithm]) | ||
|
||
@property | ||
def default_algorithm(self) -> str: | ||
return "HS256" | ||
|
||
def encode(self, to_encode, secret_key) -> str: | ||
token = self.jwt.encode(header={"alg": self.algorithm}, payload=to_encode, key=secret_key) | ||
return token.decode() # convert to string | ||
|
||
def decode(self, token, secret_key, auto_error) -> Optional[Dict[str, Any]]: | ||
try: | ||
payload = self.jwt.decode(token, secret_key) | ||
payload.validate(leeway=10) | ||
return dict(payload) | ||
except ExpiredTokenError as e: # type: ignore[attr-defined] | ||
if auto_error: | ||
raise HTTPException( | ||
status_code=HTTP_401_UNAUTHORIZED, detail=f"Token time expired: {e}" | ||
) | ||
else: | ||
return None | ||
except (InvalidClaimError, | ||
InvalidTokenError, | ||
DecodeError) as e: # type: ignore[attr-defined] | ||
if auto_error: | ||
raise HTTPException( | ||
status_code=HTTP_401_UNAUTHORIZED, detail=f"Wrong token: {e}" | ||
) | ||
else: | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from fastapi import HTTPException | ||
from typing import Any, Dict, Optional | ||
from starlette.status import HTTP_401_UNAUTHORIZED | ||
|
||
from jose import jwt | ||
|
||
from .abstract_backend import AbstractJWTBackend | ||
|
||
|
||
class PythonJoseJWTBackend(AbstractJWTBackend): | ||
|
||
def __init__(self, algorithm) -> None: | ||
self.algorithm = algorithm if algorithm is not None else self.default_algorithm | ||
assert ( | ||
hasattr(jwt.ALGORITHMS, self.algorithm) is True # type: ignore[attr-defined] | ||
), f"{algorithm} algorithm is not supported by python-jose library" | ||
|
||
@property | ||
def default_algorithm(self) -> str: | ||
return jwt.ALGORITHMS.HS256 | ||
|
||
def encode(self, to_encode, secret_key) -> str: | ||
return jwt.encode(to_encode, secret_key, algorithm=self.algorithm) | ||
|
||
def decode(self, token, secret_key, auto_error) -> Optional[Dict[str, Any]]: | ||
try: | ||
payload: Dict[str, Any] = jwt.decode( | ||
token, | ||
secret_key, | ||
algorithms=[self.algorithm], | ||
options={"leeway": 10}, | ||
) | ||
return payload | ||
except jwt.ExpiredSignatureError as e: # type: ignore[attr-defined] | ||
if auto_error: | ||
raise HTTPException( | ||
status_code=HTTP_401_UNAUTHORIZED, detail=f"Token time expired: {e}" | ||
) | ||
else: | ||
return None | ||
except jwt.JWTError as e: # type: ignore[attr-defined] | ||
if auto_error: | ||
raise HTTPException( | ||
status_code=HTTP_401_UNAUTHORIZED, detail=f"Wrong token: {e}" | ||
) | ||
else: | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.