Refresh Tokens

These are long-lived tokens which can be used to create a new access tokens once an old access token has expired. Refresh tokens cannot access an endpoint that is protected with paseto_required(), and access tokens cannot access an endpoint that is protected with paseto_required(refresh_token=True).

Access tokens are marked as fresh if they were generated from the user authenticating with their user credentials, rather than a refresh token. This increases the chance of the authenticating entity actually being the user, rather than an attacker that stole a refresh token.

By utilizing refresh tokens we can reduce the damage that can be done if an access tokens is stolen. However, if an attacker gets ahold of a refresh token they can keep generating new access tokens and access protected endpoints as though they were that user. We can help combat this by using the fresh tokens pattern, discussed in the next section.

For accessing /refresh endpoint remember to change the token you send in a request from an access_token to a refresh_token Authorization: Bearer <refresh_token>

Here is an example of using access and refresh tokens:

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


@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")

    # Use create_access_token() and create_refresh_token() to create our
    # access and refresh tokens
    access_token = Authorize.create_access_token(subject=user.username)
    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()):
    """
    The paseto_required(refresh_token=True) function insures a valid refresh
    token is present in the request before running any code below that function.
    We can use the get_subject() function to get the subject of the refresh
    token, and use the create_access_token() function again to make a new access token
    """
    Authorize.paseto_required(refresh_token=True)

    current_user = Authorize.get_subject()
    new_access_token = Authorize.create_access_token(subject=current_user)
    return {"access_token": new_access_token}


@app.get("/protected")
def protected(Authorize: AuthPASETO = Depends()):
    Authorize.paseto_required()

    current_user = Authorize.get_subject()
    return {"user": current_user}