JWT authentication in FastAPI using OAuth2 Password Flow is fundamentally about securely delegating access to your API resources without clients needing to manage long-lived credentials.

Here’s a FastAPI application demonstrating JWT authentication with OAuth2 Password Flow.

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta

# --- Configuration ---
SECRET_KEY = "your-super-secret-key-change-this-in-production" # In production, use environment variables
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# --- User Management (In-memory for simplicity) ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

users_db = {
    "testuser": {
        "username": "testuser",
        "hashed_password": pwd_context.hash("password123")
    }
}

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_user(username: str):
    return users_db.get(username)

# --- JWT Token Generation ---
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

# --- OAuth2 Scheme ---
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# --- FastAPI App ---
app = FastAPI()

@app.post("/token", summary="Generate JWT Token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = get_user(form_data.username)
    if not user or not verify_password(form_data.password, user["hashed_password"]):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user["username"]}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", summary="Get current user's info")
async def read_users_me(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
    except JWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
    user = get_user(username)
    if user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    return user

@app.get("/items/", summary="Get a list of items (protected)")
async def read_items(token: str = Depends(oauth2_scheme)):
    # In a real app, you'd verify the token and then fetch data based on the user
    # The token verification is implicitly done by Depends(oauth2_scheme)
    # and the try-except block in read_users_me demonstrates how to extract user info.
    return [{"item_id": 1, "name": "Example Item"}]

To run this:

  1. Save the code as main.py.
  2. Install dependencies: pip install fastapi uvicorn python-jose passlib bcrypt
  3. Run the server: uvicorn main:app --reload

Now, you can interact with the API. The /token endpoint is where the magic happens. You send a POST request with username and password in the form data.

curl -X POST "http://127.0.0.1:8000/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=testuser&password=password123"

This will return a JSON response containing your access_token:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0dXNlciIsImV4cCI6MTY3ODg4NjQ2MH0.some_signature",
  "token_type": "bearer"
}

You then use this access_token to access protected resources like /items/ or /users/me. You pass it in the Authorization header as a Bearer token:

curl -X GET "http://127.0.0.1:8000/items/" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"

If the token is valid and not expired, you’ll get the protected data. If it’s invalid or expired, you’ll get a 401 Unauthorized error.

The core of this system is the create_access_token function, which takes user data (here, just the username) and embeds it into a JSON Web Token (JWT). This token is signed using a SECRET_KEY and an algorithm (HS256). The access_token_expires timedelta ensures that these tokens have a limited lifespan, a crucial security feature.

The OAuth2PasswordBearer dependency (oauth2_scheme) is what tells FastAPI how to extract the token from incoming requests. By default, it looks for a Bearer token in the Authorization header. When you access a route that depends on oauth2_scheme, FastAPI automatically handles the extraction and passes the token to your route function.

The login_for_access_token endpoint is the entry point for obtaining the JWT. It receives the user’s credentials, verifies them against a (in this case, hardcoded) user database, and if successful, generates and returns the JWT. The read_users_me and read_items endpoints demonstrate how to protect routes by making them depend on the oauth2_scheme. Inside these routes, you typically decode the JWT to extract user information (sub claim for subject, which is the username here) and use that information to authorize the request. The try-except JWTError block is essential for catching malformed or invalid tokens.

The passlib library is used here for secure password hashing (bcrypt). It’s vital never to store plain-text passwords. The CryptContext object manages the hashing and verification process.

The SECRET_KEY is the single most important piece of information for JWT security. Anyone with this key can forge tokens. In a real-world application, this key must be kept secret and ideally managed via environment variables or a secrets management system. The ACCESS_TOKEN_EXPIRE_MINUTES determines how long a token is valid, balancing security with user experience.

A common misconception is that JWTs are inherently encrypted. They are typically only signed. Signing ensures the integrity and authenticity of the token (i.e., it hasn’t been tampered with and it was issued by the server), but the payload (like the username) is still Base64 encoded and easily readable. If you need to transmit sensitive data within the token, you should encrypt the token itself, or use separate mechanisms.

The next step after implementing JWT authentication is often managing refresh tokens, which are used to obtain new access tokens without requiring the user to re-enter their credentials, extending the user’s session duration securely.

Want structured learning?

Take the full Fastapi course →