# Authentication and Authorization with FastAPI: A Complete Guide


[FastAPI](https://fastapi.tiangolo.com/) is a fast and easy Python framework for building APIs. It’s as fast as Node.js and Go, with built-in support for authentication, type checking, and auto-generated docs.

Authentication verifies who a user is, while authorization controls what they can do. These are essential for protecting user data and preventing unauthorized access.

This guide will show you how to add secure authentication and authorization to your FastAPI app—from basic logins to OAuth2 with JWT.

[ad-logs]

## Prerequisites

Before you begin, make sure you have Python 3.8 or higher and `pip` installed. This guide assumes you know basic Python, web development, and REST APIs. Some knowledge of JWT tokens and OAuth2 is helpful, but don’t worry—we’ll explain them as we go.


## Getting started with FastAPI authentication

To follow along with this tutorial effectively, you'll create a new FastAPI project from scratch. 


Start by setting up your development environment:

```command
mkdir fastapi-auth && cd fastapi-auth
```

```command
python3 -m venv venv
```
```command
source venv/bin/activate
```

Now, install the required dependencies for your authentication system:

```command
pip install "fastapi" "uvicorn" "python-jose[cryptography]" "passlib[bcrypt]" "python-multipart"
```

Here's what each package does in your authentication system:

- `fastapi` provides the web framework foundation
- `uvicorn` serves as your ASGI server for running the application
- `python-jose` handles JWT token creation and validation
- `passlib` with `bcrypt` manages secure password hashing
- `python-multipart` enables form data parsing for login endpoints

Create a new `main.py` file in your project directory and add this foundational code:

```python
[label main.py]
from fastapi import FastAPI

app = FastAPI(title="Authentication Demo", version="1.0.0")

@app.get("/")
async def root():
    return {"message": "Welcome to FastAPI Authentication Demo"}
```

Start the development server to make sure everything works correctly:

```command
uvicorn main:app --reload
```

Navigate to `http://localhost:8000` in your browser, and you should see the welcome message:

![Screenshot of the web browser](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/3a27c921-3c86-40bb-181e-fcdd140d9200/orig =3248x1996)


You can also visit `http://localhost:8000/docs` to explore FastAPI's automatic interactive API documentation powered by Swagger UI:

![Screenshot of the FastAPI docs](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/4b2eef85-3318-48a0-961f-6cd3d44cd600/md2x =3248x1996)

Once that’s working, you’re ready to move on to the next step.


## Understanding FastAPI's security framework

FastAPI features a built-in security system that adheres to the OpenAPI standard. It makes it easy to add different types of authentication like API keys, HTTP Basic authentication, OAuth2 with JWT tokens, and even custom options.

It uses dependency injection to handle security. This means you can create reusable functions for authentication and apply them to specific endpoints or entire groups of routes. This helps keep your code clean and makes it easier to manage access across your app.

FastAPI also automatically updates your API documentation with the correct security information. This helps others understand how to connect to your API and what kind of authentication is needed.

## Implementing basic authentication

To start, we’ll use HTTP Basic authentication. This method isn’t recommended for production due to its security limitations, but it helps learn how FastAPI’s security system works.

Create a new file named `auth.py` and add the following code:

```python
[label auth.py]
import secrets
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

security = HTTPBasic()

def authenticate_user(credentials: HTTPBasicCredentials = Depends(security)):
    # In a real application, you'd verify against a database
    correct_username = secrets.compare_digest(credentials.username, "admin")
    correct_password = secrets.compare_digest(credentials.password, "secret")
    
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    return credentials.username
```

Now update your `main.py` file to use this authentication:

```python
[label main.py]
[highlight]
from fastapi import FastAPI, Depends
from auth import authenticate_user
[/highlight]

app = FastAPI(title="Authentication Demo", version="1.0.0")

@app.get("/")
async def root():
    return {"message": "Welcome to FastAPI Authentication Demo"}

[highlight]
@app.get("/protected")
async def protected_route(current_user: str = Depends(authenticate_user)):
    return {"message": f"Hello {current_user}, this is a protected route!"}
[/highlight]
```

The `authenticate_user` function shows several important concepts. You use `secrets.compare_digest()` for secure string comparison, which prevents timing attacks by ensuring the comparison takes a constant amount of time regardless of where the strings differ. The `HTTPException` with a 401 status code properly signals authentication failure to the client.

When you visit the `/protected` endpoint in your browser or API documentation, you'll get prompted to enter credentials. The browser will display a login dialog, and you'll need to enter "**admin**" as the username and "**secret**" as the password to access the protected resource:

![Screenshot here of the login prompt and response](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/cf137c5c-b154-4918-09c7-fb6f5e458800/md2x =3248x1996)

After entering the correct credentials, you'll be redirected to the protected route and see a message like this:

![Successfully accessed the protected route after logging in with valid credentials](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/dac962cd-3fd1-4d38-edcb-b72a309b3b00/md2x =3248x1996)

Now that you’ve seen how basic authentication works, let’s move on to making it more secure.

## Password hashing and user management

Production apps should never store passwords in plain text. Instead, you need to hash passwords using secure algorithms. Let's build a better user system with proper password hashing.

First, create a `models.py` file to define your user data structures:

```python
[label models.py]
from pydantic import BaseModel
from typing import Optional

class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None

class UserInDB(User):
    hashed_password: str

class UserCreate(BaseModel):
    username: str
    email: str
    full_name: str
    password: str
```

Now, let's start building the authentication system. Replace your `auth.py` file with the imports and configuration:

```python
[label auth.py]
from datetime import datetime, timedelta
from typing import Optional
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from models import User, UserInDB

# Configuration
SECRET_KEY = "your-secret-key-here"  # In production, use environment variables
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# Password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# OAuth2 scheme
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
```

This sets up the basic configuration. The `CryptContext` handles password hashing using bcrypt, and `OAuth2PasswordBearer` prepares us for JWT token authentication (which we'll add next).

Next, add the fake database and password functions:

```python
[label auth.py]
...

# Fake database - replace with real database in production
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",  # secret
        "disabled": False,
    }
}

def verify_password(plain_password, hashed_password):
    """Verify a password against its hash."""
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    """Generate a hash for a password."""
    return pwd_context.hash(password)
```

The fake database contains a user with a pre-hashed password. The hashed password corresponds to the plain text "secret". The two functions handle password hashing and verification using bcrypt.

Finally, add the user lookup and authentication functions:

```python
[label auth.py]
...

def get_user(db, username: str):
    """Get a user from the database."""
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

def authenticate_user(fake_db, username: str, password: str):
    """Check if username and password are correct."""
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user
```

These functions handle user lookup and authentication. `get_user` finds a user in the database, and `authenticate_user` checks if the provided username and password are correct by verifying the hashed password.

Now your authentication system uses secure password hashing and is ready for the next step where you will implement JWT tokens.


## JWT token-based authentication

JSON Web Tokens (JWT) provide a secure way to authenticate users without storing sessions on the server. Each token contains all the user information needed, making your API stateless and scalable.

Let's add JWT token creation to your `auth.py` file:

```python
[label auth.py]
[highlight]
from datetime import datetime, timedelta, timezone
[/highlight]
from typing import Optional
from passlib.context import CryptContext
...

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """Create a JWT access token."""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt
```

This function creates a JWT token with an expiration time. The token includes the user data and is signed with your secret key to prevent tampering. We use `datetime.now(datetime.timezone.utc)` for proper timezone-aware UTC timestamps.

Next, add the token validation functions:

```python
[label auth.py]
...

async def get_current_user(token: str = Depends(oauth2_scheme)):
    """Get the current user from the JWT token."""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    
    user = get_user(fake_users_db, username=username)
    if user is None:
        raise credentials_exception
    return user

async def get_current_active_user(current_user: User = Depends(get_current_user)):
    """Get the current active user (not disabled)."""
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user
```

These functions extract the user information from the JWT token and verify it's valid. The `get_current_active_user` function adds an extra check to make sure the user account isn't disabled.

Now update your `main.py` file to create a login endpoint and use JWT authentication:

```python
[label main.py]
[highlight]
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from datetime import timedelta
from auth import (
    authenticate_user, create_access_token, get_current_active_user,
    fake_users_db, ACCESS_TOKEN_EXPIRE_MINUTES
)
from models import User
[/highlight]

app = FastAPI(title="Authentication Demo", version="1.0.0")

@app.get("/")
async def root():
    return {"message": "Welcome to FastAPI Authentication Demo"}

[highlight]
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    """Authenticate user and return access token."""
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        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", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    """Get current user information."""
    return current_user
[/highlight]

@app.get("/protected")
[highlight]
async def protected_route(current_user: User = Depends(get_current_active_user)):
    return {"message": f"Hello {current_user.full_name}, this is a protected route!"}
/highlight]
```

The `/token` endpoint accepts username and password, then returns a JWT token. The `/users/me` endpoint shows how to get the current user's information using the token.

Now restart your server and visit `http://localhost:8000/docs`. You'll see the new authentication system in action:

![Screenshot showing the FastAPI docs with the new token endpoint and the authorize button](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/32d00140-6bc4-4b6f-4f49-b1fbe5ca7800/lg1x =3248x1996)

Click the "Authorize" button, enter "johndoe" as username and "secret" as password, then try accessing the protected routes:

![Screenshot showing the authorize button](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/5704e64d-fa89-4072-1a71-f544e59db000/public =3248x1306)

Fill in the login form with your credentials:

![Screenshot of the login form filled](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/11f8adcf-20eb-415d-1924-db4718814400/md2x =3248x1996)

You'll see how JWT tokens work seamlessly with FastAPI's automatic documentation. Once authorized, you can access protected routes:

![Screenshot showing successful JWT authentication and accessing protected routes](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/dc99a6a6-fb28-4720-1317-2286565e8500/orig =3248x1996)

Your API now uses secure JWT tokens instead of basic authentication, making it ready for production use.

## Working with a database

While our examples have used in-memory storage, production applications need persistent data storage. Let's integrate with a real database using SQLAlchemy and show you how to implement authentication with proper database operations.

First, install the required database dependencies:

```command
pip install "sqlalchemy" "databases[sqlite]" "alembic"
```

For this tutorial, we'll use SQLite since it doesn't require additional setup. In production, you'd typically use PostgreSQL or MySQL.

Create a `database.py` file for your database configuration:

```python
[label database.py]
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, Table, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.sql import func

# Database URL - SQLite for development
DATABASE_URL = "sqlite:///./auth.db"

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

# Association table for many-to-many relationship between users and roles
user_roles = Table(
    'user_roles',
    Base.metadata,
    Column('user_id', Integer, ForeignKey('users.id')),
    Column('role_id', Integer, ForeignKey('roles.id'))
)

class DBUser(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    email = Column(String, unique=True, index=True)
    full_name = Column(String)
    hashed_password = Column(String)
    is_active = Column(Boolean, default=True)
    created_at = Column(DateTime, default=func.now())
    
    roles = relationship("DBRole", secondary=user_roles, back_populates="users")

class DBRole(Base):
    __tablename__ = "roles"
    
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, unique=True, index=True)
    description = Column(String)
    
    users = relationship("DBUser", secondary=user_roles, back_populates="roles")

# Create tables
Base.metadata.create_all(bind=engine)

def get_db():
    """Dependency to get database session."""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
```

This sets up your database schema with proper relationships between users and roles. The `get_db` function provides a database session for your endpoints.

Next, create database operations in a `crud.py` file:

```python
[label crud.py]
from sqlalchemy.orm import Session
from passlib.context import CryptContext
from database import DBUser, DBRole
from models import UserCreate
from typing import Optional

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

def get_user_by_id(db: Session, user_id: int):
    """Get user by ID."""
    return db.query(DBUser).filter(DBUser.id == user_id).first()

def get_user_by_username(db: Session, username: str):
    """Get user by username."""
    return db.query(DBUser).filter(DBUser.username == username).first()

def get_user_by_email(db: Session, email: str):
    """Get user by email."""
    return db.query(DBUser).filter(DBUser.email == email).first()

def create_user(db: Session, user: UserCreate):
    """Create a new user."""
    hashed_password = pwd_context.hash(user.password)
    db_user = DBUser(
        username=user.username,
        email=user.email,
        full_name=user.full_name,
        hashed_password=hashed_password
    )
    
    # Assign default user role
    user_role = db.query(DBRole).filter(DBRole.name == "user").first()
    if user_role:
        db_user.roles.append(user_role)
    
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

def authenticate_user(db: Session, username: str, password: str):
    """Authenticate user with username and password."""
    user = get_user_by_username(db, username)
    if not user:
        return False
    if not pwd_context.verify(password, user.hashed_password):
        return False
    return user

def create_role(db: Session, name: str, description: str = ""):
    """Create a new role."""
    db_role = DBRole(name=name, description=description)
    db.add(db_role)
    db.commit()
    db.refresh(db_role)
    return db_role

def get_all_users(db: Session, skip: int = 0, limit: int = 100):
    """Get all users with pagination."""
    return db.query(DBUser).offset(skip).limit(limit).all()
```

These functions handle all your database operations, from creating users to authentication.

Now, update your authentication system to work with the database. Update your `auth.py` file. First, remove the old fake database and functions that are no longer needed:


```python
[label auth.py]
[highlight]
# Remove this entire section:
fake_users_db = {
    "johndoe": {
        ...    }
}

def verify_password(plain_password, hashed_password):
    ...

def get_password_hash(password):
    ...

def get_user(db, username: str):
    ...

def authenticate_user(fake_db, username: str, password: str):
    ...
[/highlight]
```
Then, update the imports and add the new database functions:

```python
[label auth.py]
from datetime import datetime, timedelta, timezone
from typing import Optional
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
[highlight]
from sqlalchemy.orm import Session
from database import get_db, DBUser
[/highlight]
from models import User, UserInDB
import crud

# Configuration
SECRET_KEY = "your-secret-key-here"  # In production, use environment variables
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# Password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# OAuth2 scheme
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

[highlight]
def convert_db_user_to_user(db_user: DBUser) -> User:
    """Convert database user to Pydantic user model."""
    return User(
        username=db_user.username,
        email=db_user.email,
        full_name=db_user.full_name,
        disabled=not db_user.is_active,
        roles=[role.name for role in db_user.roles]
    )
[/highlight]

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """Create a JWT access token."""
    ...
    return encoded_jwt

[highlight]
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
[/highlight]
    """Get the current user from the JWT token."""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    
    [highlight]
    db_user = crud.get_user_by_username(db, username=username)
    if db_user is None:
        raise credentials_exception
    return convert_db_user_to_user(db_user)
    [/highlight]

async def get_current_active_user(current_user: User = Depends(get_current_user)):
    """Get the current active user (not disabled)."""
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user
```

The key changes are using the database session and converting between database models and Pydantic models.

Finally, update your `main.py` file to use the database:

```python
[label main.py]
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from datetime import timedelta
[highlight]
from sqlalchemy.orm import Session
from database import get_db
import crud
from auth import (
   create_access_token, get_current_active_user,
    ACCESS_TOKEN_EXPIRE_MINUTES
)
from models import User, UserCreate
[/highlight]

app = FastAPI(title="FastAPI Authentication with Database", version="1.0.0")

@app.get("/")
async def root():
    return {"message": "Welcome to FastAPI Authentication Demo"}

[highlight]
@app.post("/register", response_model=User)
async def register_user(user: UserCreate, db: Session = Depends(get_db)):
    """Register a new user."""
    # Check if user already exists
    db_user = crud.get_user_by_username(db, username=user.username)
    if db_user:
        raise HTTPException(status_code=400, detail="Username already registered")
    
    db_user = crud.get_user_by_email(db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    
    new_user = crud.create_user(db=db, user=user)
    return User(
        username=new_user.username,
        email=new_user.email,
        full_name=new_user.full_name,
        disabled=not new_user.is_active,
        roles=[role.name for role in new_user.roles]
    )
[/highlight]

@app.post("/token")
[highlight]
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
    """Authenticate user and return access token."""
    user = crud.authenticate_user(db, form_data.username, form_data.password)
    [/highlight]
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    ....
```

Now you need to create the initial roles in your database. Create a simple script to set up your database:

```python
[label setup_db.py]
from sqlalchemy.orm import Session
from database import SessionLocal, engine, Base
import crud

# Create tables
Base.metadata.create_all(bind=engine)

def init_db():
    db = SessionLocal()
    
    # Create roles if they don't exist
    if not crud.get_role_by_name(db, "user"):
        crud.create_role(db, "user", "Regular user")
    
    if not crud.get_role_by_name(db, "admin"):
        crud.create_role(db, "admin", "Administrator")
    
    if not crud.get_role_by_name(db, "moderator"):
        crud.create_role(db, "moderator", "Moderator")
    
    db.close()
    print("Database initialized successfully!")

if __name__ == "__main__":
    init_db()
```

You'll also need to add the missing function to `crud.py`:

```python
[label crud.py]
...

def get_role_by_name(db: Session, name: str):
    """Get role by name."""
    return db.query(DBRole).filter(DBRole.name == name).first()
```

Run the setup script to initialize your database:

```command
python setup_db.py
```

```text
[output]
Database initialized successfully!
```

Now restart your server and test the new database-backed authentication system:

```command
uvicorn main:app --reload
```

Visit `http://localhost:8000/docs` to see the new `/register` endpoint in your API documentation:

![Screenshot showing the FastAPI docs with the new /register endpoint visible in the list](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/57139859-4dfe-42c2-e354-9ee0005e1200/lg2x =3248x1996)

Test the registration by clicking on the `/register` endpoint and trying to register a new user:

![Screenshot showing the /register endpoint expanded with the request body form](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/d51ab5b2-15cb-4a02-7445-d67042558500/md1x =3248x1996)

Fill in the registration form with sample data like:

```json
{
  "username": "testuser",
  "email": "test@example.com", 
  "full_name": "Test User",
  "password": "testpassword123"
}
```

![Screenshot showing the registration form filled out with sample data](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/06350dd7-1f5b-484a-6bf5-ed8a882b3f00/lg2x =3248x1996)

After successful registration, you'll see the created user response:

![Screenshot showing successful user registration response](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/16154ba7-9a80-4a48-4115-309132be7000/lg1x =3024x2865)

Now test logging in with your newly registered user. Go to the `/token` endpoint and use the credentials you just created:

![Screenshot showing login with the newly registered user credentials](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/8fe8143e-724b-4c9c-e032-480da7077200/orig =3035x3367)

Your authentication system now uses a real database for persistent storage, making it ready for production deployment!


## Next steps

You've built a complete authentication system with FastAPI that includes user registration, JWT tokens, and database integration. Your API now has the foundation needed for real-world applications.

To take your authentication further, explore FastAPI's documentation on [advanced security topics](https://fastapi.tiangolo.com/advanced/security/) including OAuth2 scopes, dependency injection patterns, and middleware integration. The [database documentation](https://fastapi.tiangolo.com/tutorial/sql-databases/) covers more advanced SQLAlchemy patterns and async database operations.

For production deployments, review the [deployment guide](https://fastapi.tiangolo.com/deployment/) which covers environment configuration, HTTPS setup, and containerization best practices.