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.