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.config 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.config 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.config 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.config 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