Skip to content

Latest commit

Β 

History

62 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FastAPI JWT Harmony 🎡

Python 3.11+ FastAPI PyPI version License: MIT Code Quality

A modern, type-safe JWT authentication library for FastAPI with Pydantic integration - bringing harmony to your auth flow! 🎢

πŸ”‘ Key Features

  • πŸ”’ Type-safe JWT authentication with full Pydantic model support
  • πŸš€ FastAPI dependency injection - automatic JWT validation
  • πŸ“ Multiple token locations - headers, cookies, or both
  • πŸ›‘οΈ CSRF protection for cookie-based authentication
  • 🌐 WebSocket support with dedicated authentication methods
  • πŸ‘€ User claims as Pydantic models - strongly typed user data
  • 🚫 Token denylist/blacklist support for logout functionality
  • πŸ” Asymmetric algorithms support (RS256, ES256, etc.)
  • βœ… 100% test coverage with comprehensive test suite

πŸš€ Quick Start

Installation

pip install fastapi-jwt-harmony

For asymmetric algorithm support:

pip install fastapi-jwt-harmony[asymmetric]

Basic Example

from fastapi import FastAPI, Depends
from pydantic import BaseModel
from fastapi_jwt_harmony import JWTHarmony, JWTHarmonyDep, JWTHarmonyBare

app = FastAPI()

# Define your user model
class User(BaseModel):
    id: str
    username: str
    email: str

# Configure JWT (simple way with dict)
JWTHarmony.configure(
    User,
    {
        "secret_key": "your-secret-key",  # pragma: allowlist secret
        "token_location": {"headers", "cookies"}  # Support both
    }
)

# Or use JWTHarmonyConfig for advanced configuration
# from fastapi_jwt_harmony import JWTHarmonyConfig
# JWTHarmony.configure(
#     User,
#     JWTHarmonyConfig(
#         secret_key="your-secret-key",  # pragma: allowlist secret
#         token_location={"headers", "cookies"}
#     )
# )

@app.post("/login")
def login(Authorize: JWTHarmony[User] = Depends(JWTHarmonyBare)):
    # Authenticate user (your logic here)
    user = User(id="123", username="john", email="john@example.com")

    # Create tokens
    access_token = Authorize.create_access_token(user_claims=user)
    refresh_token = Authorize.create_refresh_token(user_claims=user)

    # Set cookies (optional)
    Authorize.set_access_cookies(access_token)
    Authorize.set_refresh_cookies(refresh_token)

    return {"access_token": access_token}

@app.get("/protected")
def protected_route(Authorize: JWTHarmony[User] = Depends(JWTHarmonyDep)):
    # JWT automatically validated by JWTHarmonyDep
    current_user = Authorize.user_claims  # Typed User model!
    return {"user": current_user, "message": f"Hello {current_user.username}!"}

πŸ“¦ Dependencies Overview

FastAPI JWT Harmony provides several dependency types for different authentication needs:

from fastapi_jwt_harmony import (
    JWTHarmonyDep,      # Requires valid access token
    JWTHarmonyOptional, # Optional JWT validation
    JWTHarmonyRefresh,  # Requires valid refresh token
    JWTHarmonyFresh,    # Requires fresh access token
    JWTHarmonyBare,     # No automatic validation
)

@app.get("/public")
def public_endpoint(Authorize: JWTHarmony[User] = Depends(JWTHarmonyOptional)):
    if Authorize.user_claims:
        return {"message": f"Hello {Authorize.user_claims.username}!"}
    return {"message": "Hello anonymous user!"}

@app.post("/sensitive-action")
def sensitive_action(Authorize: JWTHarmony[User] = Depends(JWTHarmonyFresh)):
    # Requires fresh token (just logged in)
    return {"message": "Sensitive action performed"}

πŸͺ Cookie Authentication

Enable secure cookie-based authentication with CSRF protection:

from fastapi import Response

JWTHarmony.configure(
    User,
    {
        "secret_key": "your-secret-key",  # pragma: allowlist secret
        "token_location": {"cookies"},
        "cookie_csrf_protect": True,
        "cookie_secure": True,  # HTTPS only
        "cookie_samesite": "strict"
    }
)

@app.post("/login")
def login(response: Response, Authorize: JWTHarmony[User] = Depends(JWTHarmonyBare)):
    user = User(id="123", username="john", email="john@example.com")
    access_token = Authorize.create_access_token(user_claims=user)

    # Set secure cookies
    Authorize.set_access_cookies(access_token, response)
    return {"message": "Logged in successfully"}

@app.post("/logout")
def logout(response: Response, Authorize: JWTHarmony[User] = Depends(JWTHarmonyDep)):
    Authorize.unset_jwt_cookies(response)
    return {"message": "Logged out successfully"}

🌐 WebSocket Authentication

Authenticate WebSocket connections with dedicated methods:

from fastapi import WebSocket, Query
from fastapi_jwt_harmony import JWTHarmonyWS, JWTHarmonyWebSocket

@app.websocket("/ws")
async def websocket_endpoint(
    websocket: WebSocket,
    token: str = Query(...),
    Authorize: JWTHarmonyWS = Depends(JWTHarmonyWebSocket)
):
    await websocket.accept()
    try:
        # Validate JWT token
        Authorize.jwt_required(token)
        user = Authorize.user_claims

        await websocket.send_text(f"Hello {user.username}!")
    except Exception as e:
        await websocket.send_text(f"Authentication failed: {str(e)}")
        await websocket.close()

🚫 Token Denylist (Logout)

Implement secure logout with token blacklisting:

# In-memory denylist (use Redis in production)
denylist = set()

def check_if_token_revoked(jwt_payload: dict) -> bool:
    jti = jwt_payload.get("jti")
    return jti in denylist

# Configure with denylist callback
JWTHarmony.configure(
    User,
    {
        "secret_key": "your-secret-key",  # pragma: allowlist secret
        "denylist_enabled": True,
        "denylist_token_checks": {"access", "refresh"}
    },
    denylist_callback=check_if_token_revoked
)

@app.post("/logout")
def logout(Authorize: JWTHarmony[User] = Depends(JWTHarmonyDep)):
    jti = Authorize.get_jti()
    denylist.add(jti)  # Add to denylist
    return {"message": "Successfully logged out"}

βš™οΈ Configuration Options

Comprehensive configuration with sensible defaults:

from datetime import timedelta

JWTHarmonyConfig(
    # Core settings
    secret_key="your-secret-key",           # Required for HS256  # pragma: allowlist secret
    algorithm="HS256",                      # JWT algorithm
    token_location={"headers"},             # Where to look for tokens

    # Token expiration
    access_token_expires=timedelta(minutes=15),
    refresh_token_expires=timedelta(days=30),

    # Headers
    header_name="Authorization",
    header_type="Bearer",

    # Cookies
    cookie_secure=False,                    # Set True for HTTPS
    cookie_csrf_protect=True,               # CSRF protection
    cookie_samesite="strict",

    # Asymmetric keys (for RS256, ES256, etc.)
    private_key=None,                       # For signing
    public_key=None,                        # For verification

    # Denylist
    denylist_enabled=False,
    denylist_token_checks={"access", "refresh"},

    # Validation
    decode_leeway=0,                        # Clock skew tolerance
    decode_audience=None,                   # Expected audience
    decode_issuer=None,                     # Expected issuer
)

πŸ” Asymmetric Algorithms

Support for RS256, ES256, and other asymmetric algorithms:

# Generate keys (example)
private_key = """-----BEGIN PRIVATE KEY-----  # pragma: allowlist secret
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7...
-----END PRIVATE KEY-----"""  # pragma: allowlist secret

public_key = """-----BEGIN PUBLIC KEY-----  # pragma: allowlist secret
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu7...
-----END PUBLIC KEY-----"""  # pragma: allowlist secret

JWTHarmony.configure(
    User,
    JWTHarmonyConfig(
        algorithm="RS256",
        private_key=private_key,  # For signing tokens  # pragma: allowlist secret
        public_key=public_key,    # For verifying tokens  # pragma: allowlist secret
    )
)

πŸ§ͺ Testing

Run the comprehensive test suite:

# Install development dependencies
uv sync --dev

# Run tests
uv run pytest

# Run with coverage
uv run pytest --cov=fastapi_jwt_harmony

πŸ› οΈ Development

# Clone the repository
git clone https://github.com/ivolnistov/fastapi-jwt-harmony.git
cd fastapi-jwt-harmony

# Install with development dependencies
uv sync --dev

# Run tests
uv run pytest

# Run linting
uv run ruff check src/
uv run mypy src/fastapi_jwt_harmony
uv run pylint src/fastapi_jwt_harmony

πŸ“Š Project Status

  • βœ… 111 tests passing - Comprehensive test coverage
  • βœ… Type-safe - Full mypy compatibility
  • βœ… Modern Python - Supports Python 3.11+
  • βœ… Production ready - Used in production applications

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • FastAPI for the amazing web framework
  • Pydantic for data validation and settings management
  • PyJWT for JWT implementation
  • Original fastapi-jwt-auth for inspiration

Made with ❀️ for the FastAPI community

About

FastAPI JWT Harmony - Lightweight JWT authentication for FastAPI with support for access/refresh tokens, WebSockets, CSRF protection, token denylist, and both header/cookie auth. Features type-safe user claims with Pydantic models, flexible token locations, and comprehensive JWT operations. Easy to integrate and configure.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages