python

FastAPI Security: Have You Mastered OAuth2 and JWT Yet?

Unlock the Power of OAuth2 and JWT for Rock-Solid FastAPI Protection

FastAPI Security: Have You Mastered OAuth2 and JWT Yet?

When looking to secure your FastAPI application, integrating OAuth2 for third-party authentication stands out as a solid choice. OAuth2 is a popular standard that lets users give third-party apps access to their resources without handing over their passwords. Here’s a simple guide to help you set up OAuth2 with FastAPI for secure third-party authentication.

Understanding OAuth2

OAuth2 is built for scenarios where third-party apps access user resources. It has different flows to cater to various needs. For most cases, the Password Flow suffices. In this flow, the client sends the username and password to the server, which then returns an access token for future authentication requests.

Setting Up OAuth2 with FastAPI

First things first, you need a few libraries. FastAPI plays well with fastapi.security and pyjwt for OAuth2 and JWT tokens. So go ahead and install these:

pip install fastapi uvicorn pyjwt passlib[bcrypt]

Basic Setup

Start by creating a basic FastAPI app and define the OAuth2 scheme using OAuth2PasswordBearer. This handles the authentication flow for you.

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
    return {"token": token}

In this snippet, tokenUrl="token" is where users will send their username and password to get a token. Make sure to replace it with your actual endpoint.

Implementing Authentication with JWT

For a more secure setup, you can use JSON Web Tokens (JWT). Let’s dive in:

from datetime import datetime, timedelta
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from passlib.context import CryptContext
from pydantic import BaseModel

SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: str | None = None

class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None

class UserInDB(User):
    hashed_password: str

fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "[email protected]",
        "hashed_password": pwd_context.hash("your_password"),
        "disabled": False,
    },
}

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

def get_password_hash(password):
    return pwd_context.hash(password)

def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user

def create_access_token(data: dict, expires_delta: timedelta | None = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta if expires_delta else timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(token: str = Depends(oauth2_scheme)):
    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
        token_data = TokenData(username=username)
    except InvalidTokenError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

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

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    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 = create_access_token(
        data={"sub": user.username}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

How It Works

  1. Authentication Flow: When a client hits the /token endpoint with a username and password, login_for_access_token checks the credentials and gives a JWT token if they match.
  2. Token Verification: The get_current_user function checks the JWT token sent in the Authorization header of new requests. If it’s valid, it returns the user data; if not, it throws an error.
  3. Active User Check: The get_current_active_user ensures the authenticated user isn’t disabled.

Security Best Practices

HTTPS Adoption: Always use HTTPS to encrypt data in transit. You can set SSL certificates while running your FastAPI app with Uvicorn:

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8000, ssl_keyfile="path/to/key.pem", ssl_certfile="path/to/cert.pem")

Dependency Management: Regularly update all external libraries and dependencies to fend off known vulnerabilities.

Safe Session Management: Secure cookies and encrypted session stores should be your go-to. This stops unauthorized access to user sessions.

Input Validation: Always validate and sanitize user inputs to thwart web attacks like SQL injection, XSS, and CSRF.

Security Headers and CORS: Set up appropriate security headers and tweak CORS settings to reduce cross-origin request risks and other web vulnerabilities.

Error Handling: Secure error handling and logging practices can prevent leakage of sensitive information.

Conclusion

Securing your FastAPI application with OAuth2 and JWT tokens is a sturdy method for handling authentication and authorization. Following these steps and best practices ensures your web service is protected and sturdy against possible threats. Security is an ever-evolving process, and keeping up with the latest practices is crucial for the integrity of your application.

Keywords: FastAPI, OAuth2, authentication, JWT, security, third-party, user resources, HTTPS, tokens, libraries



Similar Posts
Blog Image
Python AST Manipulation: How to Modify Code on the Fly

Python's Abstract Syntax Tree manipulation allows dynamic code modification. It parses code into a tree structure, enabling analysis, transformation, and generation. This powerful technique enhances code flexibility and opens new programming possibilities.

Blog Image
Could FastAPI Be the Key to Turbo-Charging Your Web Applications?

Maximizing Backend Efficiency with Hybrid FastAPI Solutions

Blog Image
Why Is Pagination the Secret Sauce for Your FastAPI Projects?

Splitting the Data Universe: Enhancing API Performance and User Experience with FastAPI Pagination

Blog Image
Could FastAPI and SQLAlchemy Be the Ultimate Duo for Your Next Web App?

Combining FastAPI and SQLAlchemy for Scalable Web Applications

Blog Image
Unleash Marshmallow’s True Power: Master Nested Schemas for Complex Data Structures

Marshmallow: Python library for handling complex data. Nested schemas simplify serialization of hierarchical structures. Versatile for JSON APIs and databases. Supports validation, transformation, and inheritance. Efficient for large datasets. Practice key to mastery.

Blog Image
Under the Hood: Implementing a Custom Garbage Collector in Python

Python's garbage collection automates memory management. Custom implementations like reference counting, mark-and-sweep, and generational GC offer insights into memory optimization and efficient coding practices.