Use secure HTTP-only cookies for token storage in web applications
Cookie-based authentication stores JWT tokens in HTTP-only cookies, providing better security for web applications by preventing JavaScript access to tokens. This protects against XSS attacks while maintaining a seamless user experience.
from datetime import timedeltafrom authx import AuthX, AuthXConfigauth_config = AuthXConfig( JWT_ALGORITHM="HS256", JWT_SECRET_KEY="your-secret-key", # Enable cookie-based authentication JWT_TOKEN_LOCATION=["cookies"], # Cookie settings JWT_COOKIE_SECURE=True, # Only send over HTTPS (disable for local dev) JWT_COOKIE_HTTP_ONLY=True, # Prevent JavaScript access JWT_COOKIE_SAMESITE="lax", # CSRF protection ("strict", "lax", or "none") JWT_COOKIE_DOMAIN=None, # Set to your domain in production JWT_COOKIE_MAX_AGE=None, # Use session cookies (None) or set max age # Cookie names JWT_ACCESS_COOKIE_NAME="access_token_cookie", JWT_REFRESH_COOKIE_NAME="refresh_token_cookie", # Cookie paths JWT_ACCESS_COOKIE_PATH="/", JWT_REFRESH_COOKIE_PATH="/", # CSRF protection (required for cookies) JWT_COOKIE_CSRF_PROTECT=True, JWT_CSRF_IN_COOKIES=True, JWT_CSRF_METHODS=["POST", "PUT", "PATCH", "DELETE"], # CSRF cookie names and headers JWT_ACCESS_CSRF_COOKIE_NAME="csrf_access_token", JWT_ACCESS_CSRF_HEADER_NAME="X-CSRF-TOKEN", JWT_REFRESH_CSRF_COOKIE_NAME="csrf_refresh_token", JWT_REFRESH_CSRF_HEADER_NAME="X-CSRF-TOKEN",)auth = AuthX(config=auth_config)
Set JWT_COOKIE_SECURE=False for local development (HTTP). Always use True in production with HTTPS.
Use cookies for refresh tokens and headers for access tokens:
auth_config = AuthXConfig( JWT_ALGORITHM="HS256", JWT_SECRET_KEY="your-secret-key", # Accept tokens from both locations JWT_TOKEN_LOCATION=["headers", "cookies"], # Cookie settings for refresh tokens JWT_COOKIE_SECURE=False, JWT_COOKIE_HTTP_ONLY=True, JWT_COOKIE_SAMESITE="lax", JWT_COOKIE_CSRF_PROTECT=True,)auth = AuthX(config=auth_config)@app.post("/login")def login(data: LoginRequest, response: Response): """Login: return access token in body, set refresh token in cookie.""" if data.username in USERS and USERS[data.username]["password"] == data.password: # Access token in response body (client stores in memory) access_token = auth.create_access_token(uid=data.username) # Refresh token in HTTP-only cookie (secure storage) refresh_token = auth.create_refresh_token(uid=data.username) auth.set_refresh_cookies(refresh_token, response) return { "access_token": access_token, "token_type": "bearer" } raise HTTPException(status_code=401, detail="Invalid credentials")@app.get("/protected")async def protected_route(request: Request): """Protected route: access token from Authorization header.""" # Get access token from headers only access_token = await auth.get_access_token_from_request( request, locations=["headers"] ) payload = auth.verify_token(access_token, verify_csrf=False) return {"message": "Access granted", "user": payload.sub}@app.post("/refresh")async def refresh_token(request: Request): """Refresh: get refresh token from cookie, return new access token.""" # Get refresh token from cookies only refresh_token = await auth.get_refresh_token_from_request( request, locations=["cookies"] ) payload = auth.verify_token(refresh_token, verify_type=True) # Return new access token in body new_access_token = auth.create_access_token(uid=payload.sub) return {"access_token": new_access_token, "token_type": "bearer"}
Best practice: Store access tokens (short-lived) in memory and refresh tokens (long-lived) in HTTP-only cookies. This provides a balance between security and convenience.