Skip to content

JWT Authentication Middleware

Imagine you're running a members-only club. Instead of checking IDs at every door, you give members a special wristband when they enter. This wristband proves they're allowed to be there, and staff can verify it instantly without checking the guest list every time.

JWT (JSON Web Tokens) work the same way. They're digital wristbands that prove a user is authenticated, eliminating the need to check credentials on every request.

What You'll Learn

  • How JWT authentication works with Mongoz
  • Configuring the JWTAuthMiddleware
  • Protecting routes with authentication
  • Best practices for JWT security

Quick Start

from ravyn import Ravyn
from ravyn.config import JWTConfig
from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware
from accounts.documents import User

app = Ravyn(
    middleware=[
        JWTAuthMiddleware(
            config=JWTConfig(secret="your-secret-key"),
            user=User
        )
    ]
)

JWTAuthMiddleware

This middleware extends the BaseAuthMiddleware and enables JWT-based authentication for your Mongoz documents.

from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware

How It Works

  1. User logs in → Receives JWT token
  2. User makes request → Includes token in Authorization header
  3. Middleware validates → Checks token signature and expiration
  4. User injectedrequest.user contains authenticated user
  5. Route handler → Access user data directly

Configuration

Parameters

Parameter Type Description Required
app ASGI Any ASGI app instance (e.g., Ravyn) Yes
config JWTConfig JWT configuration object Yes
user Type[Document] User document class (not instance!) Yes

JWTConfig

See JWTConfig documentation for complete configuration options.


Usage Examples

from ravyn import RavynSettings
from ravyn.core.config.jwt import JWTConfig
from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware, JWTAuthBackend
from monkay import load
from lilya.middleware import DefineMiddleware as LilyaMiddleware


class CustomSettings(RavynSettings):
    @property
    def jwt_config(self) -> JWTConfig:
        """
        A JWT object configuration to be passed to the application middleware
        """
        return JWTConfig(signing_key=self.secret_key, auth_header_types=["Bearer", "Token"])

    @property
    def middleware(self) -> list[LilyaMiddleware]:
        """
        Initial middlewares to be loaded on startup of the application.
        """
        return [
            LilyaMiddleware(
                JWTAuthMiddleware,
                backend=JWTAuthBackend(
                    config=self.jwt_config,
                    user_model=load("myapp.models.User"),
                ),
            )
        ]

This approach centralizes configuration and makes it reusable across your application.

Via Application Instantiation

from lilya.middleware import DefineMiddleware as LilyaMiddleware
from monkay import load

from ravyn import Ravyn
from ravyn.conf import settings
from ravyn.core.config.jwt import JWTConfig
from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware, JWTAuthBackend

jwt_config = JWTConfig(signing_key=settings.secret_key, auth_header_types=["Bearer", "Token"])

jwt_auth_middleware = LilyaMiddleware(
    JWTAuthMiddleware,
    backend=JWTAuthBackend(
        config=jwt_config,
        user_model=load("myapp.models.User"),
    ),
)

app = Ravyn(middleware=[jwt_auth_middleware])

Direct instantiation is useful for simple applications or testing.

Via Custom Middleware

Override the middleware for advanced customization:

from lilya.types import ASGIApp
from lilya.middleware import DefineMiddleware

from ravyn import Ravyn
from ravyn.conf import settings
from ravyn.core.config.jwt import JWTConfig
from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware, JWTAuthBackend
from monkay import load


class AppAuthMiddleware(JWTAuthMiddleware):
    """
    Overriding the JWTAuthMiddleware
    """

    jwt_config = JWTConfig(signing_key=settings.secret_key, auth_header_types=["Bearer", "Token"])

    def __init__(self, app: "ASGIApp"):
        super().__init__(
            app,
            backend=JWTAuthBackend(
                config=self.jwt_config,
                user_model=load("myapp.models.User"),
            ),
        )


app = Ravyn(
    middleware=[
        DefineMiddleware(
            AppAuthMiddleware,
        )
    ]
)
from lilya.middleware import DefineMiddleware

from ravyn import RavynSettings
from ravyn.conf import settings
from ravyn.core.config.jwt import JWTConfig
from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware, JWTAuthBackend
from monkay import load
from lilya.types import ASGIApp


class AppAuthMiddleware(JWTAuthMiddleware):
    """
    Overriding the JWTAuthMiddleware
    """

    jwt_config = JWTConfig(signing_key=settings.secret_key, auth_header_types=["Bearer", "Token"])

    def __init__(self, app: "ASGIApp"):
        super().__init__(
            app,
            backend=JWTAuthBackend(
                config=self.jwt_config,
                user_model=load("myapp.models.User"),
            ),
        )


class AppSettings(RavynSettings):
    @property
    def middleware(self) -> list[DefineMiddleware]:
        """
        Initial middlewares to be loaded on startup of the application.
        """
        return [
            DefineMiddleware(
                AppAuthMiddleware,
            )
        ]

Protecting Routes

Automatic Protection

Once middleware is added, all routes under that middleware are protected:

from ravyn import get, Request

@get("/profile")
async def get_profile(request: Request) -> dict:
    # request.user is automatically available
    return {
        "email": request.user.email,
        "name": request.user.first_name
    }

Selective Protection

Apply middleware only to specific routes:

from ravyn import Ravyn, Include, get
from ravyn.contrib.auth.mongoz.middleware import JWTAuthMiddleware

# Public routes (no auth)
@get("/")
async def home() -> dict:
    return {"message": "Welcome"}

# Protected routes
@get("/dashboard")
async def dashboard(request: Request) -> dict:
    return {"user": request.user.email}

app = Ravyn(routes=[
    Gateway(handler=home),  # No middleware
    Include(
        routes=[Gateway(handler=dashboard)],
        middleware=[JWTAuthMiddleware(...)]  # Auth required
    )
])

Common Pitfalls & Fixes

Pitfall 1: Missing Authorization Header

Problem: Token not included in request headers.

// Wrong - no token
fetch('/api/profile', {
    method: 'GET'
});

Solution: Include token in Authorization header:

// Correct
fetch('/api/profile', {
    method: 'GET',
    headers: {
        'Authorization': `Bearer ${token}`
    }
});

Pitfall 2: Using User Instance Instead of Class

Problem: Passing user instance instead of class.

# Wrong
user = await User.query.get(id="507f1f77bcf86cd799439011")
JWTAuthMiddleware(config=config, user=user)  # Error!

Solution: Pass the class, not an instance:

# Correct
JWTAuthMiddleware(config=config, user=User)

Pitfall 3: Expired Tokens

Problem: Tokens expire and users get 401 errors.

Solution: Implement token refresh:

# Check token expiration
# Refresh before expiry
# See JWT Config docs for refresh token implementation

See refresh token example →


Best Practices

1. Use Environment Variables for Secrets

import os
from ravyn.config import JWTConfig

config = JWTConfig(
    secret=os.getenv("JWT_SECRET"),
    access_token_lifetime=3600  # 1 hour
)

2. Set Appropriate Token Lifetimes

config = JWTConfig(
    secret="your-secret",
    access_token_lifetime=900,      # 15 minutes
    refresh_token_lifetime=604800   # 7 days
)

3. Use HTTPS in Production

JWT tokens should always be transmitted over HTTPS to prevent interception.

4. Implement Token Refresh

Provide a refresh endpoint to get new tokens without re-authentication:

Token refresh implementation →


Important note

In the examples you could see sometimes the LilyaMiddleware being used and in other you didn't. The reason behind is very simple and also explained in the middleware section.

If you need to specify parameters in your middleware then you will need to wrap it in a lilya.middleware.DefineMiddleware object to do it so.

If no parameters are needed, then you can simply pass the middleware class directly and Ravyn will take care of the rest.


Learn More


Next Steps