Skip to content

Commit

Permalink
Feat/pyjwt (#450)
Browse files Browse the repository at this point in the history
* Replace `python-jose` with `pyjwt`
* Update function signature
* Update release notes
  • Loading branch information
tarsil authored Nov 29, 2024
1 parent 98b0306 commit 54510b2
Show file tree
Hide file tree
Showing 10 changed files with 38 additions and 32 deletions.
8 changes: 4 additions & 4 deletions docs/en/docs/configurations/jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ JWT extends for JSON Web Token and it can be used with any middleware at your de

## Requirements

Esmerald uses `python-jose` and `passlib` for this JWT integration. You can install by running:
Esmerald uses `pyjwt` and `passlib` for this JWT integration. You can install by running:

```shell
$ pip install esmerald[jwt]
Expand Down Expand Up @@ -52,12 +52,12 @@ token = Token(exp=..., iat=..., sub=...)
```

The parameters are pretty standard from
<a href="https://python-jose.readthedocs.io/en/latest/" target='_blank'>Python JOSE</a> so you can feel
<a href="https://pyjwt.readthedocs.io/en/latest/" target='_blank'>Python JOSE</a> so you can feel
comfortable with.

### Generate a Token (encode)

The [token](#token-model) offers simple and standard operations to interact with `python-jose`.
The [token](#token-model) offers simple and standard operations to interact with `pyjwt`.

```python
from esmerald.security.jwt.token import Token
Expand Down Expand Up @@ -85,7 +85,7 @@ jwt_token = Token.decode(token=..., key=settings.secret_key, algorithms=["HS256"
The `Token.decode` returns a [Token](#token-model) object.

!!! Note
This functionality relies heavily on `python-jose` but it is not mandatory to use it in any way.
This functionality relies heavily on `pyjwt` but it is not mandatory to use it in any way.
You are free to use any library that suits your unique needs. Esmerald only offers some examples and alternatives.

### The claims
Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/databases/edgy/middleware.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JWT

As part of the support, Esmerald developed an authentication middleware using python-jose allowing JWT integration
As part of the support, Esmerald developed an authentication middleware using pyjwt allowing JWT integration
with the current [supported models](./models.md#user).

## JWTAuthMiddleware
Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/databases/mongoz/middleware.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JWT

As part of the support, Esmerald developed an authentication middleware using python-jose allowing JWT integration
As part of the support, Esmerald developed an authentication middleware using pyjwt allowing JWT integration
with the current [supported documents](./documents.md#user).

## JWTAuthMiddleware
Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/databases/saffier/middleware.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JWT

As part of the support, Esmerald developed an authentication middleware using python-jose allowing JWT integration
As part of the support, Esmerald developed an authentication middleware using pyjwt allowing JWT integration
with the current [supported models](./models.md#user).

## JWTAuthMiddleware
Expand Down
4 changes: 4 additions & 0 deletions docs/en/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ hide:

## Unreleased

### Changed

- Updates from python-jose to PyJWT as dependency contrib library.

### Fixed

- Fix cli detection of wrapped esmerald instances or different ASGI servers.
Expand Down
8 changes: 4 additions & 4 deletions docs/ru/docs/configurations/jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ JWT расшифровывается как JSON Web Token. Его можно и

## Зависимости

Esmerald использует `python-jose` и `passlib` для интеграции с JWT. Вы можете установить их, выполнив:
Esmerald использует `pyjwt` и `passlib` для интеграции с JWT. Вы можете установить их, выполнив:

```shell
$ pip install esmerald[jwt]
Expand Down Expand Up @@ -53,12 +53,12 @@ token = Token(exp=..., iat=..., sub=...)
```

Параметры являются стандартными для
[Python JOSE](https://python-jose.readthedocs.io/en/latest/), так что вы можете чувствовать себя комфортно,
[Python JOSE](https://pyjwt.readthedocs.io/en/latest/), так что вы можете чувствовать себя комфортно,
используя их.

### Генерация токена (кодирование)

[Токен](#token-model) предоставляет стандартные операции для взаимодействия с `python-jose`.
[Токен](#token-model) предоставляет стандартные операции для взаимодействия с `pyjwt`.

```python
from esmerald.security.jwt.token import Token
Expand Down Expand Up @@ -86,7 +86,7 @@ jwt_token = Token.decode(token=..., key=settings.secret_key, algorithms=["HS256"
Метод `Token.decode` возвращает объект [Token](#token-model).

!!! Note
Эта функциональность сильно зависит от библиотеки `python-jose`, но её использование не является обязательным.
Эта функциональность сильно зависит от библиотеки `pyjwt`, но её использование не является обязательным.
Вы можете использовать любую библиотеку, которая соответствует вашим требованиям.
Esmerald просто предлагает примеры и альтернативы.

Expand Down
4 changes: 2 additions & 2 deletions esmerald/contrib/auth/common/middleware.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import TypeVar

from jose import JWSError, JWTError
from jwt.exceptions import PyJWTError
from lilya._internal._connection import Connection
from lilya.types import ASGIApp

Expand Down Expand Up @@ -82,7 +82,7 @@ async def authenticate(self, request: Connection) -> AuthResult:
key=self.config.signing_key,
algorithms=[self.config.algorithm],
)
except (JWSError, JWTError) as e:
except PyJWTError as e:
raise AuthenticationError(str(e)) from e

user = await self.retrieve_user(token.sub)
Expand Down
29 changes: 17 additions & 12 deletions esmerald/security/jwt/token.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Union

from jose import JWSError, JWTError, jwt
from jose.exceptions import JWSAlgorithmError, JWSSignatureError
import jwt
from jwt.exceptions import PyJWTError
from pydantic import BaseModel, Field, conint, constr, field_validator

from esmerald.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -47,35 +47,40 @@ def validate_sub(cls, subject: Union[str, int]) -> str: # pragma: no cover
raise ValueError(f"{subject} is not a valid string.") from e

def encode(
self, key: str, algorithm: str, **claims_extra: Any
self,
key: str,
algorithm: str,
claims_extra: Union[Dict[str, Any], None] = None,
**kwargs: Any,
) -> Union[str, Any]: # pragma: no cover
"""
Encodes the token into a proper str formatted and allows passing kwargs.
"""
claims: Dict = {**self.model_dump(exclude_none=True), **claims_extra}
if claims_extra is None:
claims_extra = {}

payload: Dict = {**self.model_dump(exclude_none=True), **claims_extra}
try:
return jwt.encode(
claims=claims,
payload=payload,
key=key,
algorithm=algorithm,
**kwargs,
)
except (JWSError, JWTError) as e:
except PyJWTError as e:
raise ImproperlyConfigured("Error encoding the token.") from e

@classmethod
def decode(
cls, token: str, key: Union[str, Dict[str, str]], algorithms: List[str]
cls, token: str, key: Union[str, bytes, jwt.PyJWK], algorithms: List[str], **kwargs: Any
) -> "Token": # pragma: no cover
"""
Decodes the given token.
"""
try:
data = jwt.decode(
token=token,
key=key,
algorithms=algorithms,
options={"verify_aud": False},
jwt=token, key=key, algorithms=algorithms, options={"verify_aud": False}, **kwargs
)
except (JWSError, JWTError, JWSAlgorithmError, JWSSignatureError) as e:
except PyJWTError as e:
raise e
return cls(**data)
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ dev = [
"uvicorn[standard]>=0.24.0",
]

jwt = ["passlib==1.7.4", "python-jose>=3.3.0,<4"]
jwt = ["passlib==1.7.4", "pyjwt>=2.10.1,<3"]
schedulers = ["asyncz>=0.11.0"]
all = ["esmerald[test,dev,jwt,schedulers]", "ipython", "ptpython", "a2wsgi"]
testing = [
Expand Down Expand Up @@ -258,7 +258,7 @@ module = [
"sqlalchemy_utils.*",
"slugify.*",
"pytz",
"jose.*",
"pyjwt.*",
"mako.*",
"passlib.*",
"esmerald.contrib.auth.saffier.*",
Expand Down
7 changes: 2 additions & 5 deletions tests/middleware/complex/test_with_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import edgy
import pytest
from edgy.exceptions import ObjectNotFound
from jose.exceptions import JWTError
from jwt.exceptions import PyJWTError
from lilya.types import ASGIApp

from esmerald import APIView, Gateway, HTTPException, Request, Response, get, settings, status
Expand Down Expand Up @@ -75,9 +75,6 @@ async def retrieve_user(self, user_id) -> User:

async def authenticate(self, request: Connection) -> AuthResult:
try:
# token_raw = request.headers.get(self.config.authorization_header, None)
# token = token_raw.split(" ")[1] if token_raw else None

token = request.headers.get(self.config.api_key_header, None)
if not token:
return get_error_response(
Expand All @@ -91,7 +88,7 @@ async def authenticate(self, request: Connection) -> AuthResult:
)
user = await self.retrieve_user(token.sub)
return AuthResult(user=user)
except JWTError:
except PyJWTError:
return get_error_response(
detail="Authentication failed",
status_code=status.HTTP_401_UNAUTHORIZED,
Expand Down

0 comments on commit 54510b2

Please sign in to comment.