Freshness Tokens
The fresh-token pattern is built into this extension. You can mark some access
tokens as fresh, mark others as non-fresh, and protect a route with
paseto_required(fresh=True) so that only fresh access tokens may use it.
This is useful for higher-risk actions such as changing account information. Combining fresh-token checks with refresh tokens lets you reduce frequent password prompts while still requiring a recent login for sensitive operations.
Here is an example of how you could utilize refresh tokens with the fresh token pattern:
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.responses import JSONResponse
from fastapi_paseto import AuthPASETO
from fastapi_paseto.exceptions import AuthPASETOException
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
username: str
password: str
@AuthPASETO.load_config
def get_config():
return {"authpaseto_secret_key": "secret"}
@app.exception_handler(AuthPASETOException)
def authpaseto_exception_handler(request: Request, exc: AuthPASETOException):
return JSONResponse(status_code=exc.status_code, content={"detail": exc.message})
# Standard login endpoint. Will return a fresh access token and a refresh token
@app.post("/login")
def login(user: User, Authorize: AuthPASETO = Depends()):
if user.username != "test" or user.password != "test":
raise HTTPException(status_code=401, detail="Bad username or password")
"""
create_access_token supports an optional 'fresh' argument,
which marks the token as fresh or non-fresh accordingly.
As we just verified their username and password, we are
going to mark the token as fresh here.
"""
access_token = Authorize.create_access_token(subject=user.username, fresh=True)
refresh_token = Authorize.create_refresh_token(subject=user.username)
return {"access_token": access_token, "refresh_token": refresh_token}
@app.post("/refresh")
def refresh(Authorize: AuthPASETO = Depends()):
"""
Refresh token endpoint. This will generate a new access token from
the refresh token, but will mark that access token as non-fresh,
as we do not actually verify a password in this endpoint.
"""
Authorize.paseto_required(fresh=True)
current_user = Authorize.get_subject()
new_access_token = Authorize.create_access_token(subject=current_user, fresh=False)
return {"access_token": new_access_token}
@app.post("/fresh-login")
def fresh_login(user: User, Authorize: AuthPASETO = Depends()):
"""
Fresh login endpoint. This is designed to be used if we need to
make a fresh token for a user (by verifying they have the
correct username and password). Unlike the standard login endpoint,
this will only return a new access token, so that we don't keep
generating new refresh tokens, which entirely defeats their point.
"""
if user.username != "test" or user.password != "test":
raise HTTPException(status_code=401, detail="Bad username or password")
new_access_token = Authorize.create_access_token(subject=user.username, fresh=True)
return {"access_token": new_access_token}
# Any valid PASETO access token can access this endpoint
@app.get("/protected")
def protected(Authorize: AuthPASETO = Depends()):
Authorize.paseto_required()
current_user = Authorize.get_subject()
return {"user": current_user}
# Only fresh PASETO access token can access this endpoint
@app.get("/protected-fresh")
def protected_fresh(Authorize: AuthPASETO = Depends()):
Authorize.paseto_required(fresh=True)
current_user = Authorize.get_subject()
return {"user": current_user}