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¶
- User logs in → Receives JWT token
- User makes request → Includes token in
Authorizationheader - Middleware validates → Checks token signature and expiration
- User injected →
request.usercontains authenticated user - 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¶
Via Settings (Recommended)¶
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
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¶
- JWTConfig Reference - Complete JWT configuration
- User Documents - User document documentation
- Complete Example - Full authentication tutorial
- OWASP JWT Guide - Security best practices
Next Steps¶
- User Documents - Set up user authentication
- Complete Example - Build a full auth system
- JWTConfig - Advanced JWT configuration