> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# JWT Authentication and Role-Based Access in FastAPI

> Implement JWT token-based authentication in FastAPI — register users, hash passwords, issue access tokens, protect routes with Depends, and enforce roles.

Most real APIs need to know who is making a request and whether they're allowed to do what they're asking. This lesson implements a complete, production-ready authentication system using **JSON Web Tokens (JWT)** — the recommended approach for stateless REST APIs. You'll build user registration and login endpoints, token generation with expiry, a `get_current_user` dependency that protects routes, and role-based access control (RBAC) that restricts specific endpoints to admin users. Every piece follows the pattern used in production FastAPI applications.

***

## Authentication Models Compared

<Tabs>
  <Tab title="JWT (Recommended for APIs)">
    The server issues a signed token at login. The client stores it and sends it with every subsequent request. The server verifies the signature — no database lookup needed per request.

    **Best for:** REST APIs, SPAs, mobile apps, microservices.
    **Advantage:** Stateless, scales horizontally with no shared session storage.
  </Tab>

  <Tab title="Session-Based">
    The server creates a session record in a database and gives the client a session ID cookie. Every request requires a database lookup to validate the session.

    **Best for:** Traditional server-rendered web apps.
    **Drawback:** Stateful — requires shared session storage across server nodes.
  </Tab>

  <Tab title="HTTP Basic">
    Credentials (username + password) are sent with **every request** in the `Authorization` header, Base64-encoded.

    **Best for:** Simple internal tool or development setups only.
    **Drawback:** High risk if any request is intercepted; redundant database lookups.
  </Tab>
</Tabs>

***

## What is a JWT?

A **JSON Web Token** is a compact, URL-safe string containing signed claims (statements about a user) that the server can verify without querying a database.

A JWT has three dot-separated parts:

```text theme={null}
Header.Payload.Signature
```

```text theme={null}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
```

1. **Header** — algorithm (`HS256`) and token type (`JWT`), Base64URL-encoded.
2. **Payload** — claims: `sub` (subject/user ID), `exp` (expiry), `role`, and any other data you include.
3. **Signature** — `HMAC(base64(header) + "." + base64(payload), SECRET_KEY)`. Any change to the header or payload invalidates the signature.

<Warning>
  The payload is **only Base64URL-encoded, not encrypted.** Anyone who has the token can decode and read the payload. Never store passwords, credit card numbers, or other sensitive data inside a JWT payload.
</Warning>

***

## JWT Authentication Lifecycle

```text theme={null}
CLIENT                                                   SERVER
  │                                                        │
  ├────── 1. POST /login (username + password) ──────────>┤
  │                                                        │ — Verify credentials
  │                                                        │ — Sign JWT with SECRET_KEY
  │<───── 2. Return { access_token, token_type } ─────────┤
  │                                                        │
  │   ── Subsequent Requests ──                            │
  │                                                        │
  ├────── 3. GET /profile                                 >┤
  │          Authorization: Bearer <access_token>          │ — Decode & verify signature
  │                                                        │ — Check expiry (exp)
  │                                                        │ — Retrieve user identity (sub)
  │<───── 4. 200 OK with user data  (or 401) ─────────────┤
```

***

## Step-by-Step Implementation

### Step 1: Install Dependencies

```bash theme={null}
pip install fastapi uvicorn sqlmodel pyjwt "passlib[bcrypt]" python-multipart
```

| Package            | Purpose                                 |
| ------------------ | --------------------------------------- |
| `pyjwt`            | JWT encoding and decoding               |
| `passlib[bcrypt]`  | Secure password hashing                 |
| `python-multipart` | Allows form-based credential submission |

***

### Step 2: User Model

```python theme={null}
# models.py
from typing import Optional
from sqlmodel import SQLModel, Field

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    username: str = Field(index=True, unique=True, nullable=False)
    password: str = Field(nullable=False)   # stores the bcrypt hash
    role: str = Field(default="user")       # "user" or "admin"

class RegisterRequest(SQLModel):
    username: str
    password: str

class LoginRequest(SQLModel):
    username: str
    password: str
```

***

### Step 3: Password Hashing

Never store plain-text passwords. Use bcrypt to hash on registration and verify at login:

```python theme={null}
# security.py
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)
```

***

### Step 4: Token Generation

```python theme={null}
# jwt_handler.py
from datetime import datetime, timedelta, timezone
import jwt

SECRET_KEY = "your-extremely-secure-random-secret-key"  # use an env variable in production
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict) -> str:
    payload = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    payload.update({"exp": expire})
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
```

<Tip>
  In production, load `SECRET_KEY` from an environment variable using `os.getenv("SECRET_KEY")` or a Pydantic `BaseSettings` class. Never commit secrets to version control.
</Tip>

***

### Step 5: Registration and Login Endpoints

```python theme={null}
# routers/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session, select
from database import get_session
from models import User, RegisterRequest, LoginRequest
from security import hash_password, verify_password
from jwt_handler import create_access_token

router = APIRouter(prefix="/auth", tags=["Authentication"])


@router.post("/register", status_code=status.HTTP_201_CREATED)
def register(request: RegisterRequest, session: Session = Depends(get_session)):
    # Prevent duplicate usernames
    existing = session.exec(select(User).where(User.username == request.username)).first()
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Username already exists."
        )

    new_user = User(
        username=request.username,
        password=hash_password(request.password),
        role="user"
    )
    session.add(new_user)
    session.commit()
    return {"message": "User registered successfully."}


@router.post("/login")
def login(request: LoginRequest, session: Session = Depends(get_session)):
    user = session.exec(select(User).where(User.username == request.username)).first()

    if not user or not verify_password(request.password, user.password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid username or password."
        )

    access_token = create_access_token(
        data={
            "sub": str(user.id),
            "username": user.username,
            "role": user.role
        }
    )
    return {"access_token": access_token, "token_type": "bearer"}
```

***

### Step 6: Bearer Token Verification Dependency

This dependency extracts the JWT from the `Authorization: Bearer <token>` header, verifies its signature and expiry, and returns the authenticated user:

```python theme={null}
# dependencies.py
import jwt
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlmodel import Session
from database import get_session
from models import User
from jwt_handler import SECRET_KEY, ALGORITHM

bearer_scheme = HTTPBearer()

def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
    session: Session = Depends(get_session)
) -> User:
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id: str | None = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token payload is missing user identification."
            )
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token has expired. Please log in again."
        )
    except jwt.PyJWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication token."
        )

    user = session.get(User, int(user_id))
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Authenticated user no longer exists."
        )
    return user

# Reusable typed alias
CurrentUserDep = Annotated[User, Depends(get_current_user)]
```

***

### Step 7: Protecting Routes

Apply `get_current_user` to any endpoint that requires authentication:

```python theme={null}
# routers/users.py
from fastapi import APIRouter
from dependencies import CurrentUserDep

router = APIRouter(tags=["Users"])

# Any authenticated user can access this
@router.get("/profile")
def get_profile(current_user: CurrentUserDep):
    return {
        "id": current_user.id,
        "username": current_user.username,
        "role": current_user.role
    }
```

***

### Step 8: Role-Based Access Control (RBAC)

Use a **dependency factory** to restrict endpoints to specific roles:

```python theme={null}
# dependencies.py (continued)
from fastapi import Depends, HTTPException, status
from models import User

def require_roles(*allowed_roles: str):
    """
    Returns a dependency that checks whether the current user has one of the allowed roles.
    """
    def authorize(user: User = Depends(get_current_user)) -> User:
        if user.role not in allowed_roles:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Access denied. Insufficient role permissions."
            )
        return user
    return authorize
```

Apply role requirements directly in the route decorator:

```python theme={null}
from fastapi import APIRouter, Depends
from dependencies import get_current_user, require_roles
from models import User

router = APIRouter(tags=["Protected"])

# Requires any authenticated user
@router.get("/dashboard")
def dashboard(current_user: User = Depends(get_current_user)):
    return {"message": f"Welcome, {current_user.username}!"}

# Requires the 'admin' role
@router.get("/admin/logs")
def view_logs(admin: User = Depends(require_roles("admin"))):
    return {"message": "Admin access granted — here are the system logs."}

# Requires either 'author' or 'admin' role
@router.post("/articles")
def publish_article(creator: User = Depends(require_roles("author", "admin"))):
    return {"message": f"Article published by {creator.username}."}
```

***

## Testing in Swagger UI

<Steps>
  <Step title="Open Swagger UI">
    Navigate to `http://127.0.0.1:8000/docs`.
  </Step>

  <Step title="Register a user">
    Use `POST /auth/register` to create a test user.
  </Step>

  <Step title="Log in and copy the token">
    Use `POST /auth/login` with your credentials. Copy the `access_token` value from the JSON response.
  </Step>

  <Step title="Authorise in Swagger">
    Click the green **Authorize** 🔒 button at the top of the page. Paste your token in the value field and click **Authorize**.
  </Step>

  <Step title="Call protected endpoints">
    Now call `/profile` or any protected route — Swagger will automatically include the `Authorization: Bearer <token>` header.
  </Step>
</Steps>

***

## Summary

| Step               | What you built                                                              |
| ------------------ | --------------------------------------------------------------------------- |
| Password hashing   | `hash_password()` and `verify_password()` with bcrypt                       |
| Token creation     | `create_access_token()` with `sub`, `role`, and `exp` claims                |
| Login endpoint     | `POST /auth/login` returns `{ access_token, token_type }`                   |
| Token verification | `get_current_user` dependency decodes and validates the Bearer token        |
| Route protection   | `Depends(get_current_user)` on any endpoint that requires authentication    |
| Role enforcement   | `require_roles("admin")` dependency factory for fine-grained access control |
