Require recent authentication for sensitive operations with fresh tokens
Fresh tokens are access tokens that are marked as “fresh” when created immediately after user authentication. They’re used to ensure that sensitive operations require recent password verification, adding an extra layer of security.
Fresh tokens are ideal for protecting sensitive operations that require recent authentication:
Changing password or email
Updating payment methods
Deleting account
Accessing highly sensitive data
Administrative actions
Financial transactions
Security benefit: Even if an attacker obtains a valid access token, they cannot perform sensitive operations that require a fresh token without knowing the user’s password.
When refreshing tokens, create non-fresh access tokens:
from fastapi import Request@app.post("/refresh")async def refresh_token(request: Request): """Refresh endpoint that creates a non-fresh token.""" # Get the current token token = await auth.get_access_token_from_request(request) payload = auth.verify_token(token) # Create a new token that is NOT fresh access_token = auth.create_access_token( uid=payload.sub, fresh=False # Explicitly mark as non-fresh ) return { "access_token": access_token, "token_type": "bearer" }
Provide an endpoint for users to obtain fresh tokens:
@app.post("/auth/verify-password")def verify_password(data: LoginRequest, payload: TokenPayload = auth.ACCESS_REQUIRED): """Verify password and return a fresh token. User must be already logged in (valid token) but needs a fresh token for a sensitive operation. """ # Verify the token belongs to the user trying to authenticate if data.username != payload.sub: raise HTTPException(status_code=403, detail="Token mismatch") # Verify the password if data.username in USERS and USERS[data.username]["password"] == data.password: # Issue a new fresh token fresh_token = auth.create_access_token( uid=data.username, fresh=True ) return { "access_token": fresh_token, "token_type": "bearer", "fresh": True } raise HTTPException(status_code=401, detail="Invalid password")
# Regular API call with any valid tokenresponse = httpx.get( "http://localhost:8000/protected", headers={"Authorization": f"Bearer {access_token}"})# Success
2
Attempt sensitive operation
# Try to change passwordresponse = httpx.post( "http://localhost:8000/account/change-password", headers={"Authorization": f"Bearer {access_token}"})# Fails with 401 if token is not fresh
3
Re-authenticate for fresh token
# Verify password to get fresh tokenresponse = httpx.post( "http://localhost:8000/auth/verify-password", headers={"Authorization": f"Bearer {access_token}"}, json={"username": "user1", "password": "password1"})fresh_token = response.json()["access_token"]
4
Retry with fresh token
# Now the operation succeedsresponse = httpx.post( "http://localhost:8000/account/change-password", headers={"Authorization": f"Bearer {fresh_token}"})# Success