Refresh Tokens#
Access tokens expire quickly for security. Refresh tokens let users stay logged in without re-entering credentials.
How It Works#
- User logs in → Gets access token (short-lived) + refresh token (long-lived)
- Access token expires → User calls
/refreshwith refresh token - Server returns new access token → User continues without re-login
Complete Example#
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
from authx import AuthX, AuthXConfig, TokenPayload
app = FastAPI()
config = AuthXConfig(
JWT_SECRET_KEY="your-secret-key",
JWT_TOKEN_LOCATION=["headers"],
)
auth = AuthX(config=config)
auth.handle_errors(app)
class LoginRequest(BaseModel):
username: str
password: str
@app.post("/login")
def login(data: LoginRequest):
"""Returns both access and refresh tokens."""
if data.username == "test" and data.password == "test":
access_token = auth.create_access_token(uid=data.username)
refresh_token = auth.create_refresh_token(uid=data.username)
return {
"access_token": access_token,
"refresh_token": refresh_token,
}
raise HTTPException(401, detail="Invalid credentials")
@app.post("/refresh")
def refresh(payload: TokenPayload = Depends(auth.refresh_token_required)):
"""Exchange refresh token for new access token."""
access_token = auth.create_access_token(uid=payload.sub)
return {"access_token": access_token}
@app.get("/protected", dependencies=[Depends(auth.access_token_required)])
def protected():
"""Requires valid access token."""
return {"message": "You have access"}
Testing the Flow#
# 1. Login to get both tokens
curl -X POST -H "Content-Type: application/json" \
-d '{"username":"test", "password":"test"}' \
http://localhost:8000/login
# {"access_token": "eyJ...", "refresh_token": "eyJ..."}
# 2. Access protected route with access token
curl -H "Authorization: Bearer <access-token>" \
http://localhost:8000/protected
# {"message": "You have access"}
# 3. When access token expires, use refresh token to get a new one
curl -X POST -H "Authorization: Bearer <refresh-token>" \
http://localhost:8000/refresh
# {"access_token": "eyJ..."} (new access token)
# 4. Use new access token
curl -H "Authorization: Bearer <new-access-token>" \
http://localhost:8000/protected
# {"message": "You have access"}
Token Expiration#
Configure expiration times in AuthXConfig:
from datetime import timedelta
config = AuthXConfig(
JWT_SECRET_KEY="your-secret-key",
JWT_ACCESS_TOKEN_EXPIRES=timedelta(minutes=15), # Short-lived
JWT_REFRESH_TOKEN_EXPIRES=timedelta(days=30), # Long-lived
)
Important Notes#
Protect Your Refresh Tokens
Refresh tokens are powerful - they can generate new access tokens. Store them securely:
- Use HttpOnly cookies when possible
- Never expose in URLs or logs
- Implement token revocation for logout
Token Revocation
When a user logs out, revoke both tokens. See Token Callbacks for implementing a blocklist.
Cookie-Based Refresh (Web Apps)#
For web applications, store tokens in cookies:
from fastapi import Response
config = AuthXConfig(
JWT_SECRET_KEY="your-secret-key",
JWT_TOKEN_LOCATION=["cookies"],
JWT_COOKIE_CSRF_PROTECT=True, # Important for security!
)
auth = AuthX(config=config)
auth.handle_errors(app)
@app.post("/login")
def login(data: LoginRequest, response: Response):
if data.username == "test" and data.password == "test":
access_token = auth.create_access_token(uid=data.username)
refresh_token = auth.create_refresh_token(uid=data.username)
# Set cookies (includes CSRF tokens automatically)
auth.set_access_cookies(access_token, response)
auth.set_refresh_cookies(refresh_token, response)
return {"message": "Logged in"}
raise HTTPException(401, detail="Invalid credentials")
@app.post("/logout")
def logout(response: Response):
auth.unset_cookies(response)
return {"message": "Logged out"}
See JWT Locations for more details on cookie authentication.