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 fresh_jwt_required() function to only allow fresh tokens to access the certain endpoint.

This is useful for allowing the fresh tokens to do some critical things (such as update information user) in real case you can see in the GitHub system when user wants to delete a repository in a certain time you need login if tokens not fresh again. 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 Depends, FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from async_fastapi_jwt_auth import AuthJWT
from async_fastapi_jwt_auth.exceptions import AuthJWTException
from async_fastapi_jwt_auth.auth_jwt import AuthJWTBearer

app = FastAPI()
auth_dep = AuthJWTBearer()


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


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


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


@app.exception_handler(AuthJWTException)
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
    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")
async def login(user: User, authorize: AuthJWT = Depends(auth_dep)):
    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 = await authorize.create_access_token(
        subject=user.username, fresh=True
    )
    refresh_token = await authorize.create_refresh_token(subject=user.username)
    return {"access_token": access_token, "refresh_token": refresh_token}


@app.post("/refresh")
async def refresh(authorize: AuthJWT = Depends(auth_dep)):
    """
    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.
    """
    await authorize.jwt_refresh_token_required()

    current_user = await authorize.get_jwt_subject()
    new_access_token = await authorize.create_access_token(
        subject=current_user, fresh=False
    )
    return {"access_token": new_access_token}


@app.post("/fresh-login")
async def fresh_login(user: User, authorize: AuthJWT = Depends(auth_dep)):
    """
    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 = await authorize.create_access_token(
        subject=user.username, fresh=True
    )
    return {"access_token": new_access_token}


# Any valid JWT access token can access this endpoint
@app.get("/protected")
async def protected(authorize: AuthJWT = Depends(auth_dep)):
    await authorize.jwt_required()

    current_user = await authorize.get_jwt_subject()
    return {"user": current_user}


# Only fresh JWT access token can access this endpoint
@app.get("/protected-fresh")
async def protected_fresh(authorize: AuthJWT = Depends(auth_dep)):
    await authorize.fresh_jwt_required()

    current_user = await authorize.get_jwt_subject()
    return {"user": current_user}