Authentication
Authentication is a critical component of most web applications, enabling you to identify users, protect resources, and provide personalized experiences. Nexios provides a flexible, robust authentication system that's easy to implement and customize for your specific needs. The Nexios authentication system is built around three core components:
Authentication Middleware
: Processes incoming requests, extracts credentials, and attaches user information to the requestAuthentication Backends
: Validate credentials and retrieve user informationUser Objects
: Represent authenticated and unauthenticated users with consistent interfaces
Basic Authentication Setup
To get started with authentication in Nexios, you need to set up an authentication backend and add the authentication middleware:
from nexios import NexiosApp
from nexios.auth.middleware import AuthenticationMiddleware
from nexios.auth.backends.session import SessionAuthBackend
app = NexiosApp()
async def get_user_by_id(user_id: int):
# Load user by ID
user = await db.get_user(user_id)
auth_backend = SessionAuthBackend(authenticate_func=get_user_by_id)
# Add authentication middleware
app.add_middleware(AuthenticationMiddleware(backend=auth_backend))
Once configured, the authentication system will process each request, attempt to authenticate the user, and make the user object available via request.user
:
@app.get("/profile")
async def profile(req, res):
if req.user.is_authenticated:
return res.json({
"id": req.user.id,
"username": req.user.username,
"email": req.user.email
})
else:
return res.redirect("/login")
Authentication Middleware
The AuthenticationMiddleware
is responsible for processing each request, delegating to the configured backend for authentication, and attaching the resulting user object to the request:
from nexios.auth.middleware import AuthenticationMiddleware
from nexios.auth.backends.apikey import APIKeyBackend
# Create the authentication backend
api_key_backend = APIKeyBackend(
key_name="X-API-Key",
authenticate_func=get_user_by_api_key
)
# Add authentication middleware with the backend
app.add_middleware(AuthenticationMiddleware(backend=api_key_backend))
Middleware Process Flow
- When a request arrives, the middleware calls the authentication backend's
authenticate
method - If authentication succeeds, a user object is returned along with an authentication type
- The user is attached to
request.scope["user"]
and accessible viarequest.user
- An authentication type string is also attached to
request.scope["auth"]
- If authentication fails, an
UnauthenticatedUser
instance is attached instead
Authentication Backends
Nexios includes several built-in authentication backends and allows you to create custom backends for specific needs.
Built-in Authentication Backends
1. Session Authentication Backend
from nexios.auth.backends.session import SessionAuthBackend
session_backend = SessionAuthBackend(
user_key="user_id", # Session key for user ID
authenticate_func=get_user_by_id # Function to load user by ID
)
app.add_middleware(AuthenticationMiddleware(backend=session_backend))
The session backend:
- Checks for a user ID stored in the session (typically set during login)
- Loads the full user object using the provided loader function
- Returns an authenticated user if found, or an unauthenticated user otherwise
2. JWT Authentication Backend
from nexios.auth.backends.jwt import JWTAuthBackend
jwt_backend = JWTAuthBackend(
secret_key="your-jwt-secret-key",
algorithm="HS256", # Optional, default is HS256
token_prefix="Bearer", # Optional, default is "Bearer"
authenticate_func=get_user_by_id, # Function to load user by ID
auth_header_name="Authorization" # Optional, default is "Authorization"
)
app.add_middleware(AuthenticationMiddleware(backend=jwt_backend))
The JWT backend:
- Extracts a JWT token from the Authorization header
- Validates the token signature, expiration, etc.
- Extracts the user ID from the token claims
- Loads the full user object using the provided loader function
3. API Key Authentication Backend
from nexios.auth.backends.apikey import APIKeyBackend
async def get_user_by_api_key(api_key):
# Lookup user with the given API key
user = await db.find_user_by_api_key(api_key)
if user:
return UserModel(id=user.id, username=user.username, api_key=api_key)
return None
api_key_backend = APIKeyBackend(
key_name="X-API-Key", # Header containing the API key
user_loader=get_user_by_api_key # Function to load user by API key
)
app.add_middleware(AuthenticationMiddleware(backend=api_key_backend))
The API key backend:
- Extracts an API key from the specified header
- Loads the full user object using the provided loader function
- Returns an authenticated user if found, or an unauthenticated user otherwise
Creating a Custom Authentication Backend
You can create custom authentication backends by implementing the AuthenticationBackend
abstract base class:
from nexios.auth.base import AuthenticationBackend, BaseUser, UnauthenticatedUser
class CustomUser(BaseUser):
def __init__(self, id, username, is_admin=False):
self.id = id
self.username = username
self.is_admin = is_admin
@property
def is_authenticated(self):
return True
def get_display_name(self):
return self.username
class CustomAuthBackend(AuthenticationBackend):
async def authenticate(self, request, response):
# Extract credentials from the request
custom_header = request.headers.get("X-Custom-Auth")
if not custom_header:
# Return unauthenticated user if credentials not found
return UnauthenticatedUser(), "no-auth"
# Validate credentials
# ...authentication logic...
# If validation succeeds, return a user object and auth type
if valid_credentials:
user = CustomUser(id=123, username="example_user")
return user, "custom-auth"
# If validation fails, return unauthenticated user
return UnauthenticatedUser(), "no-auth"
User Objects and Lifecycle
User objects in Nexios implement the BaseUser
interface, ensuring a consistent API regardless of the authentication backend.
The User Lifecycle
- Request Arrival: When a request reaches your application, it initially has no user information
- Authentication Process:
- The authentication middleware calls the backend's
authenticate
method - The backend extracts credentials from the request (headers, session, etc.)
- The backend validates the credentials and retrieves or creates a user object
- The authentication middleware calls the backend's
- User Attachment: The middleware attaches the user object to the request
- Request Processing: Your handlers can access
request.user
to check authentication status and user details - Response: After your handler processes the request, the response is sent back to the client
User Object Interface
All user objects (both authenticated and unauthenticated) share a common interface:
from nexios.auth.base import BaseUser
class CustomUser(BaseUser):
def __init__(self, id, username, email):
self.id = id
self.username = username
self.email = email
@property
def is_authenticated(self):
# Always return True for authenticated users
return True
def get_display_name(self):
# Return a human-readable name
return self.username
The UnauthenticatedUser
class implements the same interface but returns False
for is_authenticated
.
Protecting Routes with Authentication
Basic Route Protection
The simplest way to protect routes is to check request.user.is_authenticated
in your handlers:
@app.get("/admin")
async def admin_dashboard(req, res):
if not req.user.is_authenticated:
return res.redirect("/login")
# Process authenticated request
return res.html("Admin Dashboard")
Using Authentication Decorators
For cleaner route protection, Nexios provides authentication decorators:
from nexios.auth.decorator import auth
@app.get("/dashboard")
@auth(["auth-type"]) #jwt , session or cusotom
async def dashboard(req, res):
# This route is only accessible to authenticated users
# Unauthenticated requests will be redirected to /login
return res.html("Dashboard")
@app.get("/api/data")
@auth(["auth-type"])
async def api_data(req, res):
# This route returns 401 Unauthorized for unauthenticated requests
return res.json({"data": "Protected API data"})
Custom Authentication Requirements
You can create custom decorators for more specific authentication needs:
from functools import wraps
def requires_admin(handler):
@wraps(handler)
async def wrapper(req, res, *args, **kwargs):
if not req.user.is_authenticated or not req.user.is_admin:
return res.json({"error": "Admin access required"}, status_code=403)
return await handler(req, res, *args, **kwargs)
return wrapper
@app.get("/admin/users")
@requires_admin
async def admin_users(req, res):
# Only admins can access this route
return res.json({"users": ["user1", "user2"]})
Authentication Flows
Session-Based Authentication
Session-based authentication is a common approach for web applications:
from nexios import NexiosApp
from nexios.auth.middleware import AuthenticationMiddleware
from nexios.auth.backends.session import SessionAuthBackend
from nexios.session.middleware import SessionMiddleware
app = NexiosApp()
app.config.secret_key = "your-secure-secret-key"
# Add session middleware first
app.add_middleware(SessionMiddleware())
# Add authentication with session backend
async def get_user_by_id(user_id):
# Query the database for the user
user = await db.get_user(user_id)
if user:
return User(
id=user.id,
username=user.username,
email=user.email,
is_admin=user.is_admin
)
return None
session_backend = SessionAuthBackend(
user_key="user_id",
authenticate_func=get_user_by_id
)
app.add_middleware(AuthenticationMiddleware(backend=session_backend))
# Login route
@app.post("/login")
async def login(req, res):
data = await req.form
username = data.get("username")
password = data.get("password")
# Verify credentials
user = await db.verify_credentials(username, password)
if not user:
return res.redirect("/login?error=invalid")
# Store user ID in session
req.session["user_id"] = user.id
return res.redirect("/dashboard")
# Logout route
@app.post("/logout")
async def logout(req, res):
# Clear session
req.session.clear()
return res.redirect("/login")
JWT-Based Authentication
JWT authentication is commonly used for APIs:
from nexios import NexiosApp
from nexios.auth.middleware import AuthenticationMiddleware
from nexios.auth.backends.jwt import JWTAuthBackend
import jwt
import time
app = NexiosApp()
jwt_secret = "your-jwt-secret-key"
# Configure JWT authentication
async def get_user_by_id(user_id):
user = await db.get_user(user_id)
if user:
return User(
id=user.id,
username=user.username,
email=user.email,
is_admin=user.is_admin
)
return None
jwt_backend = JWTAuthBackend(
secret_key=jwt_secret,
algorithm="HS256",
authenticate_func=get_user_by_id
)
app.add_middleware(AuthenticationMiddleware(backend=jwt_backend))
# Login route that returns JWT
@app.post("/api/login")
async def api_login(req, res):
data = await req.json
username = data.get("username")
password = data.get("password")
# Verify credentials
user = await db.verify_credentials(username, password)
if not user:
return res.json({"error": "Invalid credentials"}, status_code=401)
# Generate JWT token
payload = {
"sub": str(user.id),
"username": user.username,
"exp": int(time.time()) + 3600 # 1 hour expiration
}
token = jwt.encode(payload, jwt_secret, algorithm="HS256")
return res.json({
"access_token": token,
"token_type": "bearer",
"expires_in": 3600
})
# Protected API route
@app.get("/api/profile")
@auth(["jwt"])
async def api_profile(req, res):
return res.json({
"id": req.user.id,
"username": req.user.username,
"email": req.user.email
})
The authentication system in Nexios is built with modern API security practices in mind. It supports a variety of authentication methods, including JSON Web Tokens (JWT), API keys, and session-based authentication. The system is designed to be fully asynchronous, ensuring that authentication processes do not block your application's performance.