Introduction

RapidAuth is a pure backend authentication framework for FastAPI. It exposes a clean JSON REST API — no HTML pages, no server-side form rendering. You plug it into your FastAPI application and immediately get a complete, production-grade auth system.

Your frontend (React, Vue, Next.js, mobile app) talks to the endpoints over HTTP. RapidAuth handles everything else.

Philosophy

  • Backend only. Every endpoint returns JSON.
  • Zero configuration to start. One line mounts the full system.
  • Scales from dev to production. Sensible defaults during development; explicit configuration for production.
  • ORM-agnostic. Tortoise ORM 1.x and SQLAlchemy (async & sync) — auto-detected.
  • Secure by default. bcrypt/argon2, refresh rotation, rate limiting, token blacklisting out of the box.

What you get

One app.include_router(auth.router) call mounts 14 production-ready endpoints:

MethodEndpointDescription
POST/auth/registerCreate account, return token pair
POST/auth/loginLogin with username or email + password
GET/auth/meReturn current authenticated user
POST/auth/refreshIssue new token pair, revoke old refresh
POST/auth/logoutBlacklist current access token
POST/auth/change-passwordChange password (old password required)
POST/auth/reset-passwordRequest password reset email
POST/auth/reset-password/confirmConfirm reset with token + new password
GET/auth/verify-emailVerify email — returns JSON
POST/auth/verify-emailVerify email with token in body
POST/auth/resend-verificationResend verification email
POST/auth/revoke-allRevoke all refresh tokens for user
GET/auth/{provider}/loginRedirect to OAuth2 provider
GET/auth/{provider}/callbackHandle OAuth2 callback

Installation

Install RapidAuth from PyPI. Choose the extras that match your ORM.

bash
# Base package only
pip install rapidauth

# With Tortoise ORM (recommended async ORM)
pip install "rapidauth[tortoise]"

# With SQLAlchemy async or sync
pip install "rapidauth[sqlalchemy]"

# Everything (Tortoise + SQLAlchemy + uvicorn)
pip install "rapidauth[all]"

CLI scaffold

Generate a complete working project in seconds:

bash
rapidauth --default-setup sqlite              # Tortoise ORM + SQLite
rapidauth --default-setup sqlalchemy --async  # SQLAlchemy async
rapidauth --default-setup postgresql --async  # SQLAlchemy + PostgreSQL

# Generates: database.py  models.py  main.py
rapidauth init     # .env.example + auth_config.py
rapidauth version  # print installed version

Requirements

  • Python ≥ 3.10
  • FastAPI ≥ 0.100
  • Tortoise ORM ≥ 1.0 (if using Tortoise)
  • SQLAlchemy ≥ 2.0 (if using SQLAlchemy)

Project Setup

Tortoise ORM 1.x users: Use RegisterTortoise inside a lifespan, not the old register_tortoise(). See the setup below.

Tortoise ORM (recommended)

python · models.py
from tortoise import fields
from tortoise.models import Model

class User(Model):
    id          = fields.IntField(pk=True)
    username    = fields.CharField(max_length=100, unique=True)
    email       = fields.CharField(max_length=254, unique=True)
    password    = fields.CharField(max_length=200)
    is_active   = fields.BooleanField(default=True)
    is_verified = fields.BooleanField(default=True)
    roles       = fields.JSONField(default=list)
    permissions = fields.JSONField(default=list)
    created_at  = fields.DatetimeField(auto_now_add=True)

    class Meta:
        table = "users"
python · main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from tortoise.contrib.fastapi import RegisterTortoise
from rapidauth import RapidAuth
from models import User

@asynccontextmanager
async def lifespan(app):
    async with RegisterTortoise(
        app,
        db_url="sqlite://./app.db",
        modules={"models": ["models"]},
        generate_schemas=True,
    ):
        yield

app  = FastAPI(lifespan=lifespan)
auth = RapidAuth(user_model=User, jwt_secret="change-this-in-production")
app.include_router(auth.router)

get_current_user = auth.get_current_user()

@app.get("/profile")
async def profile(user = Depends(get_current_user)):
    return {"id": user.id, "username": user.username, "email": user.email}

Run with: uvicorn main:app --reload. Open http://localhost:8000/docs to see all 14 endpoints in Swagger UI.

SQLAlchemy async

python · main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker
from sqlalchemy import Boolean, Integer, String, JSON
from rapidauth import RapidAuth

engine = create_async_engine("sqlite+aiosqlite:///./app.db")
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

class Base(DeclarativeBase): pass

class User(Base):
    __tablename__ = "users"
    id          : Mapped[int]  = mapped_column(Integer, primary_key=True)
    username    : Mapped[str]  = mapped_column(String(100), unique=True)
    email       : Mapped[str]  = mapped_column(String(254), unique=True)
    password    : Mapped[str]  = mapped_column(String(200))
    is_active   : Mapped[bool] = mapped_column(Boolean, default=True)
    is_verified : Mapped[bool] = mapped_column(Boolean, default=False)
    roles       : Mapped[list] = mapped_column(JSON, default=list)

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

@asynccontextmanager
async def lifespan(app):
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield

app  = FastAPI(lifespan=lifespan)
auth = RapidAuth(user_model=User, jwt_secret="your-secret", get_db=get_db)
app.include_router(auth.router)

Authentication Flow

RapidAuth implements a standard OAuth2-style flow with JWT tokens. Here is the full lifecycle:

Registration

Client sends credentials

POST /auth/register with {"username", "email", "password"}

RapidAuth validates & creates user

Checks username/email uniqueness, hashes password with bcrypt/argon2, saves to DB.

Token pair issued

Returns access_token (15 min) and refresh_token (7 days).

Optional: verification email sent

If email_verification_required=True and email is configured, a verification link is emailed.

Login

Client sends credentials

POST /auth/login with {"username": "alice"} or {"username": "alice@example.com"}.

Rate limit check

IP checked against sliding window. 5 failed attempts triggers a 5-minute lockout.

Credentials verified

Password hashed and compared timing-safely. Checks is_active and is_verified.

Token pair returned

Fresh access_token + refresh_token. Rate limit counter reset.

Token Refresh

Client sends refresh token

POST /auth/refresh with {"refresh_token": "..."} or via cookie.

Token validated & revoked

Old refresh token removed from store immediately (rotation).

New token pair issued

Fresh access + refresh tokens returned. Any replay of the old refresh token returns 401.


JWT System

RapidAuth uses HS256 JWT tokens by default. Both access and refresh tokens carry a jti (unique ID) that enables instant revocation.

Token types

TokenDefault TTLStored inPurpose
Access token900s (15 min)Client memoryAuthenticate API requests via Bearer header
Refresh token604800s (7 days)Token storeObtain new access tokens without re-login

Configuration

python
auth = RapidAuth(
    user_model=User,
    jwt_secret="your-secret",          # shorthand

    # — or full dict —
    jwt={
        "secret":         "your-secret-min-32-chars",
        "algorithm":      "HS256",     # HS256 | HS384 | HS512
        "access_expire":  900,         # seconds
        "refresh_expire": 604800,      # seconds
    },
)

Refresh token delivery

python
auth = RapidAuth(
    ...
    # "body"   → refresh_token in JSON response body (default, easiest for SPAs)
    # "cookie" → httpOnly cookie only (more secure)
    # "both"   → token in both body and cookie
    refresh_token_mode="cookie",
    cookie={
        "httponly": True,
        "secure":   True,       # True = HTTPS only
        "samesite": "lax",      # "lax" | "strict" | "none"
        "domain":   None,       # set for cross-subdomain
    },
)

Session Management

Refresh tokens are persisted in a token store. The default is an in-memory dictionary — sufficient for development and single-process deployments.

The default in-memory store is lost on server restart. Use a Redis store for production multi-process deployments.

Custom store interface

Implement these four methods to plug in any backend:

python · redis_store.py
from datetime import datetime, timezone

class RedisTokenStore:
    def __init__(self, redis):
        self.redis = redis

    async def save(self, token_hash: str, user_id: str, expires_at: datetime) -> None:
        """Persist a refresh token hash with its owner and expiry."""
        ttl = int((expires_at - datetime.now(timezone.utc)).total_seconds())
        await self.redis.setex(f"rt:{token_hash}", ttl, user_id)

    async def get(self, token_hash: str) -> dict | None:
        """Return {"user_id": "..."} if the token exists, else None."""
        val = await self.redis.get(f"rt:{token_hash}")
        return {"user_id": val.decode()} if val else None

    async def delete(self, token_hash: str) -> None:
        """Delete a single token (called on rotation or explicit logout)."""
        await self.redis.delete(f"rt:{token_hash}")

    async def revoke_all_for_user(self, user_id: str) -> None:
        """Revoke all tokens for a user (POST /auth/revoke-all)."""
        # Implement with a user→tokens reverse index if needed
        pass

# Usage
import redis.asyncio as aioredis
redis_client = aioredis.from_url("redis://localhost")

auth = RapidAuth(
    user_model=User,
    jwt_secret="...",
    token_store=RedisTokenStore(redis_client),
)

Register User

POST /auth/register No auth required

Creates a new user account and returns a token pair. If email is configured and email_verification_required=True, a verification email is sent. Otherwise, a welcome email is sent.

Request body

FieldTypeRequiredDescription
usernamestringrequired3–100 chars, letters/digits/_ and - only
emailstringrequiredValid email address
passwordstringrequiredMinimum 8 characters

Response 201

json
{
  "access_token":  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type":    "bearer"
}
bash · curl example
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","email":"alice@example.com","password":"AlicePass1!"}'

Login

POST /auth/login No auth required · Rate limited

Authenticate with username or email and password. Rate-limited to 5 failed attempts per IP per 5 minutes.

Request body

FieldTypeDescription
usernamestringUsername or email address
passwordstringAccount password

Error responses

StatusDetail
401Invalid credentials
403Account is inactive / Email not verified
429Too many login attempts. Try again in 5 minutes.
bash
curl -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"AlicePass1!"}'

Logout

POST /auth/logout Bearer token

Blacklists the current access token's jti and clears any refresh token cookies. The token is immediately invalid — no need to wait for expiry.

bash
curl -X POST http://localhost:8000/auth/logout \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Refresh Token

POST /auth/refresh Refresh token (body or cookie)

Issues a new token pair. The old refresh token is immediately revoked — any replay attempt returns 401. Accepts token from body or refresh_token cookie.

bash
curl -X POST http://localhost:8000/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"YOUR_REFRESH_TOKEN"}'

Get Current User

GET /auth/me Bearer token required

Returns the authenticated user's profile fields.

json · response
{
  "id":          1,
  "username":    "alice",
  "email":       "alice@example.com",
  "is_active":   true,
  "is_verified": true,
  "roles":       ["user"]
}

Password Reset

Request reset

POST /auth/reset-password Anti-enumeration · always 200

Creates a 1-hour one-time reset token and sends an email. Always returns 200 regardless of whether the email is registered — this prevents user enumeration.

If reset_password_url is not configured, a dev-mode email is sent showing the raw token + curl command for testing.

Confirm reset

POST /auth/reset-password/confirm No auth required

Validates the token, updates the password, and invalidates the token. Token expires in 1 hour and is single-use.

json · request body
{"token": "ONE_TIME_TOKEN", "new_password": "NewPassword1!"}
bash · full flow
# 1. Request reset
curl -X POST http://localhost:8000/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com"}'

# 2. Confirm (token from email)
curl -X POST http://localhost:8000/auth/reset-password/confirm \
  -H "Content-Type: application/json" \
  -d '{"token":"TOKEN_FROM_EMAIL","new_password":"NewPass1!"}'

Email Verification

GET /auth/verify-email?token=… Inline verify · JSON response

Verifies the token and marks the user as verified. Returns JSON — suitable as the direct email link target in development. In production, set verify_email_url to your frontend page.

POST /auth/resend-verification Bearer token OR {"email":"…"}

Resends verification email. Accepts a Bearer token (logged-in user) OR an email in the request body (no auth required). Useful when email_verification_required=True and the user's token expired before they verified.


Google OAuth

Google OAuth 2.0

RapidAuth handles the full Google OAuth 2.0 flow. When a user authenticates with Google, RapidAuth creates or retrieves their account automatically and returns a token pair.

Step 1 — Create a Google Cloud project

1

Go to Google Cloud Console

Navigate to console.cloud.google.com and create a new project (or select an existing one).

2

Enable Google+ API

Go to APIs & Services → Library and enable the Google+ API (or People API).

3

Create OAuth credentials

Go to APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID. Choose Web application.

4

Set Authorized redirect URIs

Add your callback URL: https://api.myapp.com/auth/google/callback
For local development: http://localhost:8000/auth/google/callback

5

Copy Client ID and Secret

Download the credentials JSON or copy the Client ID and Client Secret values.

Step 2 — Configure RapidAuth

python
auth = RapidAuth(
    user_model=User,
    jwt_secret="your-secret",
    social_auth={
        "google": {
            "client_id":     "1234567890-abc.apps.googleusercontent.com",
            "client_secret": "GOCSPX-your_client_secret",
            "redirect_uri":  "https://api.myapp.com/auth/google/callback",
        }
    },
)

Step 3 — Use the endpoints

RapidAuth automatically adds these two routes:

MethodEndpointDescription
GET/auth/google/loginRedirects user to Google consent screen
GET/auth/google/callbackHandles callback, creates/logs in user, returns token pair
javascript · frontend
// Redirect user to Google login
window.location.href = "https://api.myapp.com/auth/google/login";

// After Google redirects to your callback URL, RapidAuth
// automatically handles the token exchange and returns:
// { access_token: "...", refresh_token: "...", token_type: "bearer" }

Google OAuth users are created automatically on first login with is_verified=True. A random password is generated — these users log in only via Google.


GitHub OAuth

GitHub OAuth Apps

Authenticate users with their GitHub accounts. RapidAuth uses GitHub's OAuth 2.0 flow to retrieve user info and create accounts automatically.

Step 1 — Create a GitHub OAuth App

1

Go to GitHub Settings

Navigate to github.com → Settings → Developer settings → OAuth Apps → New OAuth App.

2

Fill in the application details

Application name: Your app name
Homepage URL: https://myapp.com
Authorization callback URL: https://api.myapp.com/auth/github/callback

3

Register the application

Click Register application. Copy the Client ID.

4

Generate a Client Secret

On the app settings page, click Generate a new client secret. Copy it immediately — it won't be shown again.

Step 2 — Configure RapidAuth

python
auth = RapidAuth(
    user_model=User,
    jwt_secret="your-secret",
    social_auth={
        "github": {
            "client_id":     "Iv1.abc1234567890abc",
            "client_secret": "your_github_client_secret",
            "redirect_uri":  "https://api.myapp.com/auth/github/callback",
        }
    },
)

Flow

MethodEndpointDescription
GET/auth/github/loginRedirects to GitHub authorization page
GET/auth/github/callbackHandles code exchange, creates/logs in user

GitHub may not provide an email address for users with private emails. In that case, RapidAuth generates a placeholder: {username}@github.oauth.


Facebook Login

Meta / Facebook Login

Allow users to log in with their Facebook accounts using Meta's OAuth 2.0 implementation.

Step 1 — Create a Meta App

1

Go to Meta for Developers

Navigate to developers.facebook.com → My Apps → Create App. Choose "Allow people to log in with their Facebook account".

2

Set up Facebook Login product

In your app dashboard, click Set Up under Facebook Login. Under Valid OAuth Redirect URIs, add: https://api.myapp.com/auth/facebook/callback

3

Get App ID and Secret

Go to Settings → Basic to copy your App ID and App Secret.

4

Go Live

Toggle your app to Live mode before production. In development mode, only test users can authenticate.

Configure RapidAuth

python
auth = RapidAuth(
    user_model=User,
    jwt_secret="your-secret",
    social_auth={
        "facebook": {
            "client_id":     "1234567890123456",       # App ID
            "client_secret": "your_facebook_app_secret",
            "redirect_uri":  "https://api.myapp.com/auth/facebook/callback",
        }
    },
)

Callback Handling

RapidAuth handles the entire OAuth callback flow internally. You do not need to write any callback logic.

Complete OAuth flow

1. User initiates login

Frontend redirects user to GET /auth/{provider}/login

2. Provider redirect

RapidAuth redirects to the provider's consent screen (Google/GitHub/Facebook). User grants permission.

3. Authorization code returned

Provider redirects to GET /auth/{provider}/callback?code=…. RapidAuth receives the authorization code.

4. Token exchange

RapidAuth exchanges the code for an access token with the provider's token endpoint.

5. User info retrieved

RapidAuth calls the provider's user info endpoint to get email and username.

6. Account created or retrieved

If a user with this email exists → log them in. If not → create a new account automatically with is_verified=True.

7. Token pair returned

RapidAuth returns {"access_token":"…","refresh_token":"…"}. Frontend stores and uses these for subsequent API calls.

Handling multiple providers, same email

If a user already has a password-based account with the same email and tries to log in via OAuth, RapidAuth will find the existing account by email and log them in. No duplicate accounts are created.


Environment Variables

Never hard-code secrets in source code. Use environment variables loaded from a .env file.

env · .env
# JWT
JWT_SECRET=your-very-long-random-secret-min-32-chars
JWT_ACCESS_EXPIRE=900
JWT_REFRESH_EXPIRE=604800

# Email (SMTP)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USERNAME=you@gmail.com
EMAIL_PASSWORD=xxxx_xxxx_xxxx_xxxx
EMAIL_FROM=you@gmail.com

# URLs
BASE_URL=https://api.myapp.com
VERIFY_EMAIL_URL=https://myapp.com/verify-email
RESET_PASSWORD_URL=https://myapp.com/reset-password

# Google OAuth
GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your_secret
GOOGLE_REDIRECT_URI=https://api.myapp.com/auth/google/callback

# GitHub OAuth
GITHUB_CLIENT_ID=Iv1.abc1234567890
GITHUB_CLIENT_SECRET=your_github_secret
GITHUB_REDIRECT_URI=https://api.myapp.com/auth/github/callback

# Facebook OAuth
FACEBOOK_CLIENT_ID=1234567890123456
FACEBOOK_CLIENT_SECRET=your_facebook_secret
FACEBOOK_REDIRECT_URI=https://api.myapp.com/auth/facebook/callback

# Database
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/mydb
python · config.py
import os
from dotenv import load_dotenv

load_dotenv()

auth = RapidAuth(
    user_model=User,
    jwt_secret=os.getenv("JWT_SECRET"),
    email={
        "host":       os.getenv("EMAIL_HOST"),
        "port":       int(os.getenv("EMAIL_PORT", 587)),
        "username":   os.getenv("EMAIL_USERNAME"),
        "password":   os.getenv("EMAIL_PASSWORD"),
        "from_email": os.getenv("EMAIL_FROM"),
    },
    base_url=os.getenv("BASE_URL", "http://localhost:8000"),
    verify_email_url=os.getenv("VERIFY_EMAIL_URL"),
    reset_password_url=os.getenv("RESET_PASSWORD_URL"),
    social_auth={
        "google": {
            "client_id":     os.getenv("GOOGLE_CLIENT_ID"),
            "client_secret": os.getenv("GOOGLE_CLIENT_SECRET"),
            "redirect_uri":  os.getenv("GOOGLE_REDIRECT_URI"),
        },
        "github": {
            "client_id":     os.getenv("GITHUB_CLIENT_ID"),
            "client_secret": os.getenv("GITHUB_CLIENT_SECRET"),
            "redirect_uri":  os.getenv("GITHUB_REDIRECT_URI"),
        },
    },
)

Password Hashing

RapidAuth supports two battle-tested hashing algorithms. Both use timing-safe comparison to prevent timing attacks.

AlgorithmDefaultStrengthSpeed
bcryptdefaultVery strong (cost 12)Moderate
argon2Winner of Password Hashing Competition 2015Slightly slower
python
auth = RapidAuth(
    user_model=User,
    jwt_secret="...",
    password_hasher="bcrypt",   # "bcrypt" (default) | "argon2"
)

# Manual hashing (e.g. for seed scripts)
hashed = auth.hash_password("my-password")
valid  = auth.verify_password("my-password", hashed)  # True

Timing attack prevention: When a username is not found, RapidAuth performs a dummy hash verification to ensure the response time is the same regardless of whether the user exists.


Token Expiration

TokenDefaultConfig keyRecommendation
Access token15 min (900s)access_expireKeep short. 5–15 min for high security.
Refresh token7 days (604800s)refresh_expire7–30 days. Longer = better UX, shorter = more secure.
Email verify token24 hoursNot configurableSingle-use, invalidated on success.
Password reset token1 hourNot configurableSingle-use, invalidated on success.
python
auth = RapidAuth(
    user_model=User,
    jwt={
        "secret":         "your-secret",
        "access_expire":  300,      # 5 minutes (high-security apps)
        "refresh_expire": 2592000,  # 30 days (mobile apps)
    },
)

Rate Limiting

RapidAuth includes a built-in sliding-window rate limiter on the login endpoint to prevent brute-force attacks.

How it works

  • Tracks failed login attempts per client IP.
  • After max_login_attempts failures, the IP is locked out for lockout_seconds.
  • On successful login, the counter is reset immediately.
  • Returns HTTP 429 during lockout.
python
auth = RapidAuth(
    user_model=User,
    jwt_secret="...",
    rate_limit={
        "enabled":             True,   # default True
        "max_login_attempts":  5,      # failures before lockout
        "lockout_seconds":     300,    # 5-minute lockout window
    },
)

Forwarded IPs (X-Forwarded-For header) are respected automatically. Make sure your reverse proxy (nginx, Caddy) sets this header correctly.


Middleware Usage

RapidAuth provides an AuthMiddleware that populates request.state.user on every request — useful for logging, analytics, or global access control.

python
auth.add_middleware(app)

@app.get("/")
async def home(request: Request):
    user = request.state.user
    if user.is_authenticated:
        return {"hello": user.username}
    return {"hello": "anonymous"}

@app.middleware("http")
async def log_requests(request: Request, call_next):
    user = request.state.user
    if user.is_authenticated:
        print(f"Request from: {user.username}")
    return await call_next(request)

Unauthenticated requests get a proxy object with is_authenticated = False. Accessing any user field raises no exception — it simply returns None.

RBAC with dependency injection

python
from fastapi import Depends
from rapidauth.exceptions import PermissionDeniedError

def require_role(*roles: str):
    """Factory that returns a FastAPI dependency checking for roles."""
    async def dep(user = Depends(auth.get_current_user())):
        for role in roles:
            if role not in (user.roles or []):
                raise PermissionDeniedError(f"Role '{role}' required")
        return user
    return dep

@app.get("/admin")
async def admin(user = Depends(require_role("admin"))):
    return {"ok": True}

@app.get("/staff")
async def staff(user = Depends(require_role("admin", "moderator"))):
    return {"ok": True}

Custom Backend Integration

You can override the token store, use custom field names, or hook into the user creation flow.

Custom field names

If your User model uses different column names than the defaults:

python
auth = RapidAuth(
    user_model=User,
    jwt_secret="...",
    # Override field names to match your model
    username_field="login",        # default: "username"
    email_field="email_address",   # default: "email"
    password_field="pwd_hash",     # default: "password"
    id_field="user_id",            # default: "id"
    is_active_field="active",      # default: "is_active"
    is_verified_field="verified",  # default: "is_verified"
    roles_field="user_roles",      # default: "roles"
)

Programmatic user management

python
manager = auth.manager

# Create user (e.g. in a seed script)
user = await auth.create_user(
    "admin", "admin@example.com", "AdminPass1!",
    extra={"roles": ["admin"], "permissions": ["users.write"]},
)

# Find users
user = await manager.get_by_email("alice@example.com")
user = await manager.get_by_username("alice")
user = await manager.get_by_id(42)

# Check credentials
user = await auth.authenticate("alice", "AlicePass1!")

# Token operations
access, refresh = await manager.create_token_pair(user)
await manager.revoke_all_tokens(user)

# Password operations
hashed = auth.hash_password("password")
ok     = auth.verify_password("password", hashed)

Database Adapters

RapidAuth auto-detects your ORM by inspecting the model's class hierarchy at startup. You rarely need to configure this manually.

ORMDetectionSessionNotes
Tortoise ORMAuto (inspects __mro__)Global (via RegisterTortoise)Requires Tortoise 1.x + RegisterTortoise lifespan
SQLAlchemy asyncAutoPer-request via get_dbPass get_db=get_db to RapidAuth
SQLAlchemy syncAutoPer-request via get_dbBlocks event loop — dev only
SQLModelAutoPer-request via get_dbBuilt on SQLAlchemy — same config

Tortoise ORM 1.x — common error

RuntimeError: No TortoiseContext is currently active.
This happens when using Tortoise.init() or the old register_tortoise() without the global fallback. Fix: use RegisterTortoise inside a FastAPI lifespan as shown in the Project Setup section.

python · ✅ correct for Tortoise 1.x
from tortoise.contrib.fastapi import RegisterTortoise

@asynccontextmanager
async def lifespan(app):
    async with RegisterTortoise(          # sets _enable_global_fallback=True
        app,
        db_url="sqlite://./app.db",
        modules={"models": ["models"]},
        generate_schemas=True,
    ):
        yield

app = FastAPI(lifespan=lifespan)
python · ❌ broken with Tortoise 1.x
# DON'T use register_tortoise() or Tortoise.init() without the global fallback
from tortoise.contrib.fastapi import register_tortoise

register_tortoise(app, ...)  # broken in Tortoise 1.x — no global context set

# Also broken:
await Tortoise.init(db_url="...", modules={"models": [...]})  # no global fallback