0
0
FastapiHow-ToIntermediate · 4 min read

How to Implement Refresh Token in FastAPI Securely

To implement refresh token in FastAPI, create two JWT tokens: an access token with a short expiry and a refresh token with a longer expiry. Use an endpoint to verify the refresh token and issue a new access token without requiring user login again.
📐

Syntax

Implementing refresh tokens in FastAPI involves these parts:

  • Create JWT tokens: Generate access_token and refresh_token with different expiry times.
  • Store refresh tokens securely: Usually in HTTP-only cookies or a database.
  • Refresh endpoint: An API route that accepts the refresh token, verifies it, and returns a new access token.
  • Token verification: Decode and validate tokens using a secret key and algorithms.
python
from datetime import datetime, timedelta
from jose import JWTError, jwt

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 7

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def create_refresh_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt
💻

Example

This example shows a FastAPI app with login, token creation, and a refresh endpoint that issues a new access token using a valid refresh token.

python
from fastapi import FastAPI, HTTPException, Depends, status, Cookie
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional

app = FastAPI()

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 7

fake_users_db = {
    "user1": {
        "username": "user1",
        "password": "password123"
    }
}

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def create_refresh_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

@app.post("/login")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = fake_users_db.get(form_data.username)
    if not user or user["password"] != form_data.password:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
    access_token = create_access_token({"sub": user["username"]})
    refresh_token = create_refresh_token({"sub": user["username"]})
    return {"access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer"}

@app.post("/refresh")
async def refresh_token(refresh_token: Optional[str] = Cookie(None)):
    if refresh_token is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Refresh token missing")
    try:
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
    except JWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
    new_access_token = create_access_token({"sub": username})
    return {"access_token": new_access_token, "token_type": "bearer"}
Output
POST /login with valid credentials returns JSON with access_token and refresh_token; POST /refresh with valid refresh_token cookie returns new access_token JSON.
⚠️

Common Pitfalls

  • Not setting different expiry times: Access tokens should expire quickly; refresh tokens last longer.
  • Not verifying tokens properly: Always decode and check token validity and expiration.
  • Storing refresh tokens insecurely: Use HTTP-only cookies or secure storage to prevent theft.
  • Not handling token revocation: Consider ways to revoke refresh tokens if needed (e.g., logout).
python
## Wrong: Using same expiry for both tokens
ACCESS_TOKEN_EXPIRE_MINUTES = 60
REFRESH_TOKEN_EXPIRE_DAYS = 0  # This is wrong, refresh token should last longer

## Right: Different expiry times
ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 7
📊

Quick Reference

Tips for implementing refresh tokens in FastAPI:

  • Use jose.jwt to encode and decode JWT tokens.
  • Set short expiry for access tokens and longer for refresh tokens.
  • Store refresh tokens securely, preferably in HTTP-only cookies.
  • Provide a refresh endpoint to issue new access tokens.
  • Always validate tokens and handle errors gracefully.

Key Takeaways

Create separate access and refresh JWT tokens with different expiry times.
Use a dedicated refresh endpoint to issue new access tokens securely.
Store refresh tokens securely, ideally in HTTP-only cookies.
Always verify token validity and handle errors to prevent unauthorized access.
Implement token revocation strategies for better security.