Skip to content

JWTConfigΒΆ

Think of a JWT like a security badge at a tech company. When you arrive (log in), security gives you a badge with your photo, name, and access level encoded on it. Throughout the day, you show this badge to enter different areas - no need to go back to security each time.

The badge is:

  • Self-contained - Has all your info encoded in it
  • Tamper-proof - Security can tell if someone modified it
  • Time-limited - Expires at end of day

JWTs work the same way. After login, the server gives you a token containing your user info and permissions. You include this token with each request, and the server verifies it without checking the database every time.

Configure JWT authentication in your Ravyn application for secure, stateless authentication.

What You'll LearnΒΆ

  • What JWT is and how it works
  • Configuring JWT in Ravyn
  • Generating and validating tokens
  • Access and refresh token patterns
  • Best practices for JWT security

Quick StartΒΆ

from ravyn import Ravyn
from ravyn.core.config.jwt import JWTConfig
from ravyn.contrib.auth.edgy.middleware import JWTAuthMiddleware

app = Ravyn(
    middleware=[JWTAuthMiddleware],
    jwt_config=JWTConfig(
        signing_key="your-secret-key-change-in-production",
        algorithm="HS256"
    )
)

Info

Install JWT support: pip install ravyn[jwt]


What is JWT?ΒΆ

JWT (JSON Web Token) is a compact, URL-safe token format for transmitting information between parties. Perfect for stateless authentication.

JWT StructureΒΆ

header.payload.signature
eyJhbGc...  .  eyJzdWI...  .  SflKxw...
  • Header - Algorithm and token type
  • Payload - Claims (user data, expiration, etc.)
  • Signature - Cryptographic signature

Why Use JWT?ΒΆ

  • Stateless - No server-side session storage

  • Scalable - Works across multiple servers

  • Mobile-Friendly - Easy to use in mobile apps

  • Flexible - Include custom claims


Basic ConfigurationΒΆ

Minimal SetupΒΆ

from ravyn import Ravyn
from ravyn.core.config.jwt import JWTConfig

app = Ravyn(
    jwt_config=JWTConfig(
        signing_key="your-secret-key",
        algorithm="HS256"
    )
)

Complete ConfigurationΒΆ

app = Ravyn(
    jwt_config=JWTConfig(
        signing_key="your-secret-key",
        algorithm="HS256",
        access_token_lifetime=3600,      # 1 hour
        refresh_token_lifetime=86400,    # 24 hours
        issuer="https://api.example.com",
        audience="https://example.com"
    )
)

Configuration ParametersΒΆ

Parameter Type Description Default
signing_key str Secret key for signing tokens Required
algorithm str Signing algorithm "HS256"
access_token_lifetime int Access token TTL (seconds) 3600
refresh_token_lifetime int Refresh token TTL (seconds) 86400
issuer str Token issuer None
audience str Token audience None

Generating TokensΒΆ

Using Token ClassΒΆ

from ravyn.security.jwt.token import Token
from ravyn.conf import settings
from datetime import datetime, timedelta

# Create token with claims
token = Token(
    sub="user123",  # Subject (user ID)
    exp=datetime.utcnow() + timedelta(hours=1),  # Expiration
    iat=datetime.utcnow()  # Issued at
)

# Encode to JWT string
jwt_string = token.encode(
    key=settings.secret_key,
    algorithm="HS256"
)

Custom ClaimsΒΆ

# Add custom claims
token = Token(
    sub="user123",
    exp=datetime.utcnow() + timedelta(hours=1),
    iat=datetime.utcnow(),
    email="user@example.com",  # Custom claim
    role="admin"  # Custom claim
)

jwt_string = token.encode(
    key=settings.secret_key,
    algorithm="HS256"
)

Validating TokensΒΆ

Decode TokenΒΆ

from ravyn.security.jwt.token import Token
from ravyn.conf import settings

# Decode JWT string
token = Token.decode(
    token=jwt_string,
    key=settings.secret_key,
    algorithms=["HS256"]
)

# Access claims
user_id = token.sub
email = token.email

With ValidationΒΆ

try:
    token = Token.decode(
        token=jwt_string,
        key=settings.secret_key,
        algorithms=["HS256"],
        audience="https://example.com",
        issuer="https://api.example.com"
    )
except Exception as e:
    # Token invalid or expired
    print(f"Token validation failed: {e}")

Access & Refresh TokensΒΆ

Custom Token ClassΒΆ

from ravyn.security.jwt.token import Token
from typing import Literal

class AppToken(Token):
    token_type: Literal["access", "refresh"]

    def is_access_token(self) -> bool:
        return self.token_type == "access"

    def is_refresh_token(self) -> bool:
        return self.token_type == "refresh"

Generate Both TokensΒΆ

from datetime import datetime, timedelta

def create_tokens(user_id: str) -> dict:
    # Access token (short-lived)
    access_token = AppToken(
        sub=user_id,
        token_type="access",
        exp=datetime.utcnow() + timedelta(hours=1),
        iat=datetime.utcnow()
    )

    # Refresh token (long-lived)
    refresh_token = AppToken(
        sub=user_id,
        token_type="refresh",
        exp=datetime.utcnow() + timedelta(days=7),
        iat=datetime.utcnow()
    )

    return {
        "access_token": access_token.encode(key=settings.secret_key, algorithm="HS256"),
        "refresh_token": refresh_token.encode(key=settings.secret_key, algorithm="HS256")
    }

Authentication MiddlewareΒΆ

Using Built-in MiddlewareΒΆ

from ravyn import Ravyn
from ravyn.contrib.auth.edgy.middleware import JWTAuthMiddleware
from ravyn.core.config.jwt import JWTConfig

app = Ravyn(
    middleware=[JWTAuthMiddleware],
    jwt_config=JWTConfig(
        signing_key="your-secret-key",
        algorithm="HS256"
    )
)

Protected EndpointsΒΆ

from ravyn import get, Request

@get("/protected")
async def protected_route(request: Request) -> dict:
    # User automatically authenticated by middleware
    user = request.user
    return {"user_id": user.id, "email": user.email}

Complete ExampleΒΆ

Login EndpointΒΆ

from ravyn import post
from pydantic import BaseModel

class LoginRequest(BaseModel):
    email: str
    password: str

@post("/auth/login")
async def login(data: LoginRequest) -> dict:
    # Verify credentials (example)
    user = await User.get(email=data.email)
    if not user or not user.verify_password(data.password):
        raise HTTPException(status_code=401, detail="Invalid credentials")

    # Generate tokens
    tokens = create_tokens(str(user.id))

    return {
        "access_token": tokens["access_token"],
        "refresh_token": tokens["refresh_token"],
        "token_type": "bearer"
    }

Refresh EndpointΒΆ

@post("/auth/refresh")
async def refresh_token(refresh_token: str) -> dict:
    try:
        # Decode refresh token
        token = AppToken.decode(
            token=refresh_token,
            key=settings.secret_key,
            algorithms=["HS256"]
        )

        # Verify it's a refresh token
        if not token.is_refresh_token():
            raise HTTPException(status_code=401, detail="Invalid token type")

        # Generate new access token
        new_access_token = AppToken(
            sub=token.sub,
            token_type="access",
            exp=datetime.utcnow() + timedelta(hours=1),
            iat=datetime.utcnow()
        ).encode(key=settings.secret_key, algorithm="HS256")

        return {"access_token": new_access_token}

    except Exception as e:
        raise HTTPException(status_code=401, detail="Invalid refresh token")

Using with SettingsΒΆ

from ravyn import RavynSettings
from ravyn.core.config.jwt import JWTConfig

class AppSettings(RavynSettings):
    jwt_config: JWTConfig = JWTConfig(
        signing_key="your-secret-key",
        algorithm="HS256",
        access_token_lifetime=3600,
        refresh_token_lifetime=604800  # 7 days
    )

app = Ravyn(settings_module=AppSettings)

Common Pitfalls & FixesΒΆ

Pitfall 1: Weak Secret KeyΒΆ

Problem: Using a weak or hardcoded secret.

# Wrong - weak secret
jwt_config = JWTConfig(
    signing_key="secret"  # Too simple!
)

Solution: Use strong, random secrets:

# Correct
import secrets
import os

jwt_config = JWTConfig(
    signing_key=os.getenv("JWT_SECRET", secrets.token_urlsafe(32))
)

Pitfall 2: Long-Lived Access TokensΒΆ

Problem: Access tokens that don't expire.

# Wrong - never expires
token = Token(
    sub="user123",
    # No exp claim!
)

Solution: Always set expiration:

# Correct
from datetime import datetime, timedelta

token = Token(
    sub="user123",
    exp=datetime.utcnow() + timedelta(hours=1),
    iat=datetime.utcnow()
)

Pitfall 3: Storing Tokens in LocalStorageΒΆ

Problem: XSS vulnerability.

// Wrong - vulnerable to XSS
localStorage.setItem('token', accessToken);

Solution: Use httpOnly cookies or secure storage:

# Correct - set httpOnly cookie
from ravyn import Response

response = Response({"message": "Logged in"})
response.set_cookie(
    "access_token",
    access_token,
    httponly=True,
    secure=True,
    samesite="strict"
)

Best PracticesΒΆ

1. Use Environment VariablesΒΆ

# Good - configurable secrets
import os

jwt_config = JWTConfig(
    signing_key=os.getenv("JWT_SECRET_KEY"),
    algorithm="HS256"
)

2. Short Access Token LifetimeΒΆ

# Good - short-lived access tokens
jwt_config = JWTConfig(
    signing_key=secret_key,
    access_token_lifetime=900,  # 15 minutes
    refresh_token_lifetime=604800  # 7 days
)

3. Validate All ClaimsΒΆ

# Good - validate issuer and audience
token = Token.decode(
    token=jwt_string,
    key=settings.secret_key,
    algorithms=["HS256"],
    issuer="https://api.example.com",
    audience="https://example.com"
)

Learn MoreΒΆ


Next StepsΒΆ