Token Freshness#
Token freshness mechanisms provide additional protection for sensitive operations. Fresh tokens should only be generated when the user provides credential information during a login operation.
Every time a user authenticates itself with credentials, it receives a fresh token. However, when an access token is refreshed (implicitly or explicitly), the generated access token SHOULD NOT be marked as fresh.
Most protected endpoints should not consider the fresh state of the access token. However, in specific application cases, the use of a fresh token enables an additional layer of protection by requiring the user to authenticate itself.
These operations include but are not limited to:
- Password update
- User information update
- Privilege/Permission management
Combined with an explicit refresh mechanism#
Let's use the example from the previous section on Refreshing tokens.
from pydantic import BaseModel
from fastapi import FastAPI, Depends, HTTPException
from authx import AuthX, TokenPayload
app = FastAPI()
security = AuthX()
class LoginForm(BaseModel):
username: str
password: str
@app.post('/login')
def login(data: LoginForm):
if data.username == "test" and data.password == "test":
access_token = security.create_access_token(
data.username,
fresh=True
)
refresh_token = security.create_refresh_token(data.username)
return {
"access_token": access_token,
"refresh_token": refresh_token
}
raise HTTPException(401, "Bad username/password")
@app.post('/refresh')
def refresh(
refresh_payload: TokenPayload = Depends(security.refresh_token_required)
):
access_token = security.create_access_token(
refresh_payload.sub,
fresh=False
)
return {"access_token": access_token}
@app.get('/protected', dependencies=[Depends(security.access_token_required)])
def protected():
return "You have access to this protected resource"
@app.get('/sensitive_operation', dependencies=[Depends(security.fresh_token_required)])
def sensitive_operation():
return "You have access to this sensitive operation"
Creating fresh access tokens#
To create a fresh access token, use the fresh=True argument in the AuthX.create_access_token method.
In the /login route, we set the fresh argument to True because the token is generated after the user explicitly provides a username/password combo.
@app.post('/login')
def login(data: LoginForm):
if data.username == "test" and data.password == "test":
access_token = security.create_access_token(
data.username,
fresh=True
)
refresh_token = security.create_refresh_token(data.username)
return {
"access_token": access_token,
"refresh_token": refresh_token
}
raise HTTPException(401, "Bad username/password")
Note
By default, AuthX.create_access_token has fresh=False to avoid implicit fresh token generation.
Refreshing tokens#
When refreshing tokens, you should always generate non-fresh tokens. In the example below, the fresh argument is explicitly set to False, which is also the default behavior for AuthX.create_access_token.
@app.post('/refresh')
def refresh(
refresh_payload: TokenPayload = Depends(security.refresh_token_required)
):
access_token = security.create_access_token(
refresh_payload.sub,
fresh=False
)
return {"access_token": access_token}
Requiring fresh tokens#
The /protected route behaves as usual, regardless of the fresh token's state.
In contrast, the /sensitive_operation route requires a fresh token to be executed. This behavior is defined by the AuthX.fresh_token_required dependency.
@app.get('/protected', dependencies=[Depends(security.access_token_required)])
def protected():
return "You have access to this protected resource"
@app.get('/sensitive_operation', dependencies=[Depends(security.fresh_token_required)])
def sensitive_operation():
return "You have access to this sensitive operation"
Note on Internal server error
As you might notice, step 4 results in a 500 HTTP Internal Server Error. This is the expected behavior since error handling is by default disabled on AuthX and should be enabled or handled by you to avoid errors.