Skip to main content

JWT Token Structure

The application uses JSON Web Tokens (JWT) for session management. Tokens are stored in httpOnly cookies for security.

Token Payload

sub
string
required
Subject - User ID as a string
exp
integer
required
Expiration time - Unix timestamp when the token expires
iat
integer
required
Issued at - Unix timestamp when the token was created

Token Generation

Tokens are created using the create_access_token function in app/auth/jwt_utils.py:6.
def create_access_token(user_id: int) -> str:
    """Create a JWT token for the given user ID."""
    expire = datetime.now(timezone.utc) + timedelta(
        minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
    )
    payload = {
        "sub": str(user_id),
        "exp": expire,
        "iat": datetime.now(timezone.utc),
    }
    return jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)

Configuration

SECRET_KEY
string
required
Secret key used for signing JWT tokens. Must be kept secure.
ALGORITHM
string
default:"HS256"
Algorithm used for JWT encoding/decoding
ACCESS_TOKEN_EXPIRE_MINUTES
integer
Token expiration time in minutes

Token Validation

Tokens are validated using the decode_access_token function in app/auth/jwt_utils.py:17.
def decode_access_token(token: str) -> int | None:
    """Decode JWT and return user_id, or None if invalid/expired."""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        user_id = payload.get("sub")
        if user_id is None:
            return None
        return int(user_id)
    except JWTError:
        return None

Validation Rules

  1. Signature Verification - Token must be signed with the correct SECRET_KEY
  2. Expiration Check - Token must not be expired
  3. Subject Presence - Token must contain a “sub” claim with the user ID
  4. Algorithm Match - Token must use the configured algorithm

Return Values

user_id
integer
The user ID extracted from the token
None
null
Returned when:
  • Token signature is invalid
  • Token is expired
  • Token is malformed
  • “sub” claim is missing

Authentication Dependency

The get_current_user dependency function validates the JWT cookie and retrieves the authenticated user from the database.

Implementation

Defined in app/auth/dependencies.py:9:
async def get_current_user(
    access_token: str | None = Cookie(default=None),
    db: AsyncSession = Depends(get_db),
) -> User:
    """
    Read the httpOnly JWT cookie, decode it, and return the User.
    Raises 401 if missing, invalid, or expired.
    """
    if not access_token:
        raise HTTPException(status_code=401, detail="Not authenticated")

    user_id = decode_access_token(access_token)
    if user_id is None:
        raise HTTPException(status_code=401, detail="Invalid or expired token")

    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=401, detail="User not found")

    return user

Behavior

  1. Extracts the access_token from the httpOnly cookie
  2. Decodes and validates the JWT token
  3. Queries the database for the user by ID
  4. Returns the User object if found
  5. Raises HTTPException 401 for any authentication failure

Error Responses

401
error
Not authenticated - Cookie is missing
401
error
Invalid or expired token - JWT validation failed
401
error
User not found - User ID from token doesn’t exist in database

Logout

POST /auth/logout
Clears the authentication cookie, effectively logging out the user.

Response

message
string
Confirmation message: “Logged out”

Behavior

  • Deletes the access_token httpOnly cookie
  • Cookie deletion respects the same security settings (secure, samesite) as when it was set
  • No authentication required (can be called even if not logged in)
{
  "message": "Logged out"
}

Build docs developers (and LLMs) love