Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(security): incorrect argument to Litestar #2220

Merged
merged 5 commits into from
Aug 25, 2023

Conversation

aorith
Copy link
Contributor

@aorith aorith commented Aug 25, 2023

Pull Request Checklist

  • New code has 100% test coverage
  • (If applicable) The prose documentation has been updated to reflect the changes introduced by this PR
  • (If applicable) The reference documentation has been updated to reflect the changes introduced by this PR

Description

  • The argument request_handlers doesn't exist, it should be route_handlers

Off-topic

There is also something wrong, I think, in the example of "Implementing a JWTAuthenticationMiddleware":

class Token(BaseModel):
    exp: datetime
    iat: datetime
    sub: UUID4

According to the JWT specification, exp and iat should be represented by a "NumericDate" (see https://www.rfc-editor.org/rfc/rfc7519.html)

The example (adapted a bit because it breaks with current Pydantic version complaining that UUID is not JSON serializable), breaks if those datetime fields are represented as a string, this code example reproduces it:

import uuid
from datetime import datetime, timedelta

import msgspec
from jose import jwt
from pydantic import UUID4, BaseModel, field_serializer

ALGORITHM = "HS256"
JWT_SECRET = "_EXAMPLE_"


class Token(BaseModel):
    exp: datetime
    iat: datetime
    sub: UUID4

    @field_serializer("sub")
    def serialize_dt(self, v: UUID4):
        return str(v)


class TokenStruct(msgspec.Struct):
    exp: datetime
    iat: datetime
    sub: uuid.UUID


def encode_jwt_token_pydantic(user_id: UUID4) -> str:
    token = Token(
        exp=datetime.now() + timedelta(days=1),
        iat=datetime.now(),
        sub=user_id,
    )
    print(token.model_dump())
    return jwt.encode(token.model_dump(), JWT_SECRET, algorithm=ALGORITHM)


def encode_jwt_token_msgspec(user_id: UUID4) -> str:
    token = TokenStruct(
        exp=datetime.now() + timedelta(days=1),
        iat=datetime.now(),
        sub=user_id,
    )
    print(msgspec.to_builtins(token))
    return jwt.encode(msgspec.to_builtins(token), JWT_SECRET, algorithm=ALGORITHM)


def decode_jwt_token(encoded_token: str) -> dict:
    return jwt.decode(token=encoded_token, key=JWT_SECRET, algorithms=[ALGORITHM])


print("PYDANTIC:")
encoded_token_pydantic = encode_jwt_token_pydantic(user_id=uuid.uuid4())
decode_jwt_token(encoded_token=encoded_token_pydantic)

print("MSGSPEC:")
encoded_token_pydantic = encode_jwt_token_msgspec(user_id=uuid.uuid4())
decode_jwt_token(encoded_token=encoded_token_pydantic)

# Output:
# PYDANTIC:
# {'exp': 1693059438, 'iat': 1692973038, 'sub': 'dc4c8be9-74b9-4394-a9d9-ae3d6fb569b4'}
# MSGSPEC:
# {'exp': '2023-08-26T14:12:08.787825', 'iat': '2023-08-25T14:12:08.787828', 'sub': '38ba76c9-c156-455d-8a4f-1c9edabfdd56'}
# . . .
# jose.exceptions.JWTClaimsError: Issued At claim (iat) must be an integer.

Shouldn't the example be changed to use integers instead which seem to be less troublesome here?

import time
import uuid

import msgspec
from jose import jwt
from pydantic import UUID4, BaseModel, field_serializer

ALGORITHM = "HS256"
JWT_SECRET = "_EXAMPLE_"


class Token(BaseModel):
    exp: int
    iat: int
    sub: UUID4

    @field_serializer("sub")
    def serialize_dt(self, v: UUID4):
        return str(v)


class TokenStruct(msgspec.Struct):
    exp: int
    iat: int
    sub: uuid.UUID


def encode_jwt_token_pydantic(user_id: UUID4) -> str:
    token = Token(
        exp=int(time.time()) + 60 * 60 * 24,
        iat=int(time.time()),
        sub=user_id,
    )
    print(token.model_dump())
    return jwt.encode(token.model_dump(), JWT_SECRET, algorithm=ALGORITHM)


def encode_jwt_token_msgspec(user_id: UUID4) -> str:
    token = TokenStruct(
        exp=int(time.time()) + 60 * 60 * 24,
        iat=int(time.time()),
        sub=user_id,
    )
    print(msgspec.to_builtins(token))
    return jwt.encode(msgspec.to_builtins(token), JWT_SECRET, algorithm=ALGORITHM)


def decode_jwt_token(encoded_token: str) -> dict:
    return jwt.decode(token=encoded_token, key=JWT_SECRET, algorithms=[ALGORITHM])


print("PYDANTIC:")
encoded_token_pydantic = encode_jwt_token_pydantic(user_id=uuid.uuid4())
decode_jwt_token(encoded_token=encoded_token_pydantic)

print("MSGSPEC:")
encoded_token_pydantic = encode_jwt_token_msgspec(user_id=uuid.uuid4())
decode_jwt_token(encoded_token=encoded_token_pydantic)

# Output:
# PYDANTIC:
# {'exp': 1693059537, 'iat': 1692973137, 'sub': '78f6756d-554a-4c5f-a087-b71dcfbc16a9'}
# MSGSPEC:
# {'exp': 1693059537, 'iat': 1692973137, 'sub': '88da090f-7815-4293-adf8-ddc4baf6c68c'}

Sorry for the off-topic, hope it helps.

@aorith aorith requested review from a team as code owners August 25, 2023 14:23
@Goldziher
Copy link
Contributor

@all-contributors add @aorith docs

@allcontributors
Copy link
Contributor

@Goldziher

@aorith already contributed before to doc

@Goldziher Goldziher merged commit f227a8c into litestar-org:main Aug 25, 2023
11 checks passed
@github-actions
Copy link

Documentation preview will be available shortly at https://litestar-org.github.io/litestar-docs-preview/2220

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants