FastAPI - Beginner to Pro / Authentication & Authorization

Authenticate a User

🔐 Authentication — Validating a User Login (Step 1)

In the previous section, we successfully:

  • Created users

  • Hashed passwords using bcrypt

  • Stored users securely in the database

Now, we move to the next critical step:

Allowing a user to authenticate (log in) using their username and password.

This is the foundation of authentication.

We will build this in stages to fully understand what’s happening internally.


🎯 Goal of This Section

We want to:

  1. Accept a username and password

  2. Validate them against the database

  3. Confirm whether authentication succeeds or fails

  4. Prepare for JWT token generation (next section)


🧱 Step 1 — Create a Login Endpoint

We begin by creating a simple POST endpoint:

🗂️ routers/auth.py

@router.post("/token")
async def login_for_access_token():
    return "token"

If you run:

uvicorn main:app --reload

You’ll see the /token endpoint appear in Swagger.

Right now:

  • It accepts nothing

  • It validates nothing

  • It simply returns "token"

We need to fix that.


Required 🧩 Step 2 — Install Dependency

To submit form data in FastAPI using OAuth standards, we need:

terminal

pip install python-multipart

Why?

Because OAuth2 login uses application/x-www-form-urlencoded form data.

Without this package, FastAPI cannot process login forms.

python-multipart is required in FastAPI when your API needs to handle form data, especially:

  • multipart/form-data

  • File uploads

  • OAuth2 login forms


🛡 Step 3 — Use OAuth2PasswordRequestForm

Instead of manually accepting username and password, FastAPI provides a secure helper:

from fastapi.security import OAuth2PasswordRequestForm

This is an OAuth2-compliant login form.

It automatically handles:

  • username (required)

  • password (required)

  • grant_type

  • scope

  • client_id

  • client_secret


🧠 Why Use OAuth2PasswordRequestForm?

Because:

  • It follows OAuth2 standards

  • Swagger automatically generates a proper login form

  • It integrates cleanly with JWT authentication

  • It prepares our application for production-level security


🏗 Updated Endpoint with Dependency Injection

Now we inject it:

@router.post("/token")
async def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],):
    return form_data.username

If you test this:

  • Enter username + password

  • Execute

It returns the username.

This proves:

  • FastAPI is receiving the form data

  • Dependency injection is working

But we are not validating anything yet.


🧠 Step 4 — Create Authentication Logic

Now we create the real backend logic.

Authentication must:

  1. Look up the user by username

  2. Verify password using bcrypt

  3. Return success or failure

🔎 The Authentication Function

This helper function will be used in /token endpoint

def authenticate_user(username: str, password: str, db: Session):
    user = db.query(Users).filter(Users.username == username).first()

    if not user:
        return False

    if not bcrypt_context.verify(password, user.hashed_password):
        return False

    return user

🔍 What’s Happening Internally?

1️⃣ Query the Database

db.query(Users).filter(Users.username == username).first()

We check if a user exists.

If not → return False.

2️⃣ Verify Password

bcrypt_context.verify(password, user.hashed_password)

Important:

  • The password from the form is plain text

  • The database contains a hashed password

  • verify() hashes the plain text internally

  • Then compares it securely with the stored hash

If they match → authentication succeeds.


🧩 Step 5 — Connect Everything

Now lets use the authenticate_user helper function we created and update /token:

@router.post("/token")
async def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    db: db_dependency,):

    user = authenticate_user(form_data.username, form_data.password, db)

    if not user:
        return "Failed Authentication"

    return "Successful Authentication"

🧪 Testing Authentication

❌ Invalid Username + Correct Password

Result → Failed Authentication

❌ Correct Username + Wrong Password

Result → Failed Authentication

✅ Correct Username + Correct Password

Result → Successful Authentication


🧠 Important Backend Concepts Learned

  • OAuth2 form handling

  • Dependency injection in FastAPI

  • Secure password verification

  • Separation of concerns (route vs authentication logic)


🔐 Why This Is Only Step One

Right now we are:

✔ Validating credentials

❌ Not generating tokens yet

Next section we will:

  • Generate a JWT token

  • Encode user information

  • Return secure access tokens

  • Protect endpoints using that token


from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from database import SessionLocal
from models import Users
from passlib.context import CryptContext
from fastapi.security import OAuth2PasswordRequestForm

router = APIRouter()

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

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

db_dependency = Annotated[Session, Depends(get_db)]

def authenticate_user(username: str, password: str, db: Session):
    user = db.query(Users).filter(Users.username == username).first()

    if not user:
        return False

    if not bcrypt_context.verify(password, user.hashed_password):
        return False

    return user

@router.post("/token")
async def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    db: db_dependency,):
    
    user = authenticate_user(form_data.username, form_data.password, db)

    if not user:
        return "Failed Authentication"

    return "Successful Authentication"

No other file changed