Freshness Tokens

The fresh tokens pattern is built into this extension. This pattern is very simple, you can choose to mark some access tokens as fresh and other as a non-fresh tokens, and use the paseto_required(fresh=True) function to only allows fresh tokens to access the certain endpoint.

This is useful for allowing the fresh tokens to do some critical things (such as update user information), as you need to login using your user credentials again to get a fresh token.\ Utilizing Fresh tokens in conjunction with refresh tokens can lead to a more secure site, without creating a bad user experience by making users constantly re-authenticate.

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_auth import AuthPASETO
from fastapi_paseto_auth.exceptions import AuthPASETOException
from pydantic import BaseModel

app = FastAPI()


class User(BaseModel):
    username: str
    password: str


class Settings(BaseModel):
    authpaseto_secret_key: str = "secret"


@AuthPASETO.load_config
def get_config():
    return Settings()


@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}