Skip to main content

auth101

Simple email/password authentication for Django, FastAPI, and Flask.

Configure once, mount anywhere. auth101 uses a schema (user, account, session, verification), handles password hashing (Argon2, bcrypt fallback), JWT access/refresh tokens with a session table for revocation, and optional default Django models so you can add authentication in minutes.

Features

  • Email/password sign-up, sign-in, sign-out, and session verification
  • Access + refresh tokens with session table for revocation
  • Secure password hashing with Argon2 (bcrypt fallback)
  • JWT access/refresh token generation and verification
  • Built-in framework integrations for FastAPI, Flask, and Django
  • Pluggable storage backends: SQLAlchemy or Django ORM

Installation

pip install auth101

With extras for your framework and database:

pip install auth101[fastapi]      # FastAPI + Pydantic
pip install auth101[flask]        # Flask
pip install auth101[django]       # Django
pip install auth101[sqlalchemy]   # SQLAlchemy (any SQL database)
pip install auth101[all]          # Everything

How it works

  • Auth101 is the main entry point: you pass a secret, a database adapter (SQLAlchemy or Django), and optional entity config (table names / field mappings). Table names and field mappings are configured on Auth101, not on the adapter.
  • Internally, a factory (auth101.factory) resolves config and builds the object graph: the adapter is asked for four stores (user, account, session, verification) with the resolved table name (or Django model) and field mapping; then TokenIssuer, EmailPasswordService, and SessionService are wired and attached to the facade.
  • EmailPasswordService handles sign-up and sign-in: creates/loads user and account (password hash in account with provider_id="credential"), creates a session row (with a unique jti), and issues access + refresh JWTs.
  • SessionService handles refresh (validates refresh token, looks up session by jti, rotates by revoking old session and issuing new tokens), revoke, sign-out, and get-session. Refresh tokens are tied to a session row so they can be revoked.
  • Integrations (FastAPI, Flask, Django) expose the same HTTP endpoints and call these Auth101 methods; they also provide a way to get the current user (dependency, decorator, or middleware).

Architecture. Auth101 uses a layered structure: domain (entities) → core / security / ports → features (use cases) → adapters and factory → facade → integrations. See docs/ARCHITECTURE.md for layers, dependency rules, and explicit composition (create_auth101, build_auth101_wiring).

Quick Start

1. Install and create auth (SQLAlchemy with a DB URL):

from auth101 import Auth101
from auth101.adapters import SQLAlchemyAdapter

adapter = SQLAlchemyAdapter("sqlite:///auth.db")  # or "sqlite:///:memory:" for tests
auth = Auth101(secret="change-me-in-production", database=adapter)

2. For a real database, create the auth tables once (SQLAlchemy only):

# Config module must define database (and optionally user / user_field_mapping for user table)
auth101 migrate --config myapp.auth_config

Or omit migrate when using sqlite:///auth.db — the adapter creates tables on first use.

3. Use the API:

# Sign up → returns user + access_token + refresh_token + expires_in
result = auth.sign_up("alice@example.com", "s3cr3t")
# result["user"]: {id, name, email, email_verified, image, is_active}
# result["access_token"], result["refresh_token"], result["expires_in"]

# Sign in
result = auth.sign_in("alice@example.com", "s3cr3t")

# Call your API with the access token (Bearer)
session = auth.get_session(result["access_token"])  # {"user": {...}}

# Refresh (get new tokens; old refresh token is revoked)
auth.refresh(result["refresh_token"])

# Logout: revoke server-side session, then discard tokens
auth.revoke_session(result["refresh_token"])
auth.sign_out(result["access_token"])  # validates access token

Configuration

Pass a database adapter (connection only; no table names or field mapping on the adapter). Configure entity options on Auth101.

User table: two options (mutually exclusive)

  • user — Customize the user table that auth101 creates and manages. Use {"model_name": "users", "fields": {...}}. Migrate will create this table; you only customize its name and column names.
  • user_field_mapping — Use your own user table (you create and manage it). Must include table_name (or model_name) and optionally fields (auth101 field → your column). Auth101 never creates or migrates this table; we only use it via the mapping. If both user and user_field_mapping are provided, user wins.

Session/account/verification: if present in config, migrate skips that table; if omitted, migrate creates it with defaults.

from auth101 import Auth101
from auth101.adapters import SQLAlchemyAdapter

adapter = SQLAlchemyAdapter("sqlite:///auth.db")  # adapter takes only engine/URL
auth = Auth101(secret="...", database=adapter)

# Auth101-managed user table with custom name and column names (migrate creates it)
auth = Auth101(
    secret="...",
    database=adapter,
    user={
        "model_name": "users",
        "fields": {"name": "full_name", "email": "email_address"},
    },
)
# session, account, verification omitted → migrate will create those tables with defaults

# Your own user table (you manage it); include table_name + mapping (migrate skips user)
auth = Auth101(
    secret="...",
    database=adapter,
    user_field_mapping={
        "table_name": "app_user",
        "fields": {"email": "email_address", "name": "full_name"},
    },
)

# Custom session table only
auth = Auth101(
    secret="...",
    database=adapter,
    session={"model_name": "my_sessions"},
)
# user, account, verification: user uses default (migrate creates user); migrate creates account, verification
Parameter Description
secret Required. Secret key for signing JWT tokens.
database Required. A DatabaseAdapter (e.g. SQLAlchemyAdapter(url), DjangoAdapter(UserModel)). Do not pass table names to the adapter.
user Optional. Customize the user table auth101 creates and manages: {"model_name": "users", "fields": {...}}. Migrate creates this table.
session Optional. Entity config for session table. If present, migrate skips session.
account Optional. Entity config for account table. If present, migrate skips account.
verification Optional. Entity config for verification table. If present, migrate skips verification.
user_field_mapping Optional. Use your own user table: {"table_name": "app_user", "fields": {"email": "your_column", ...}}. Auth101 never creates or migrates it. Mutually exclusive with user; if both, user wins.
access_token_expires_in Access token TTL in minutes. Default: 60.
refresh_token_expires_in Refresh token TTL in minutes. Default: 10080 (7 days).

Explicit composition. You can build Auth101 via the factory for tests or custom wiring: from auth101.factory import create_auth101, build_auth101_wiring. create_auth101(secret=..., database=..., ...) is equivalent to Auth101(...). build_auth101_wiring(...) returns an Auth101Wiring with adapter, entity config, resolved tables/mappings, and wired services (used by the CLI for migrations). See docs/ARCHITECTURE.md.

CLI: create tables (auth101 migrate)

  • User table: Migrate creates it only when auth101 manages it (user=... or neither). If you use your own table (user_field_mapping with table_name), migrate skips the user table.
  • Session/account/verification: Migrate creates them when omitted from config; skips when present.
auth101 migrate --config myapp.auth_config

Your config module must define either auth (Auth101 instance) or database (and optionally user_field_mapping for your own user table):

# myapp/auth_config.py (preferred)
from auth101 import Auth101
from auth101.adapters import SQLAlchemyAdapter

adapter = SQLAlchemyAdapter("sqlite:///auth.db")
auth = Auth101(secret="...", database=adapter)

# Auth101-managed user with custom name: migrate creates it
# auth = Auth101(secret="...", database=adapter, user={"model_name": "users", "fields": {...}})

# Your own user table: migrate skips user
# auth = Auth101(secret="...", database=adapter, user_field_mapping={"table_name": "app_user", "fields": {...}})
# Legacy: database + user_field_mapping (your own user table; migrate skips user)
from auth101.adapters import SQLAlchemyAdapter
database = SQLAlchemyAdapter("sqlite:///auth.db")
user_field_mapping = None   # or {"table_name": "app_user", "fields": {"email": "email_address"}}

Adapters that don’t support migrate (e.g. Django) use their ORM’s migrations instead.

Core API

All methods return plain dicts, so they work with any framework or without one.

auth.sign_up(email, password)

Register a new user. Returns {"user": {...}, "access_token": "...", "refresh_token": "...", "expires_in": N} on success.

auth.sign_in(email, password)

Authenticate an existing user. Returns {"user": {...}, "access_token": "...", "refresh_token": "...", "expires_in": N} on success.

auth.refresh(refresh_token)

Exchange a refresh token for new access and refresh tokens (rotation; old refresh token is revoked). Returns the same shape as sign-in.

auth.revoke_session(refresh_token)

Revoke the session for the given refresh token (e.g. on logout). Returns {"success": True}.

auth.sign_out(token)

Validate an access token and acknowledge sign-out. Returns {"success": True}. Call revoke_session(refresh_token) to invalidate the session server-side.

auth.get_session(token)

Return the user associated with an access token. Returns {"user": {...}} on success. The user dict has: id, name, email, email_verified, image, is_active.

auth.verify_token(token)

Verify an access token and return the corresponding User object (with .id, .name, .email, .email_verified, .image, .is_active), or None.

Error Responses

On failure, methods return {"error": {"message": "...", "code": "..."}} with one of these codes:

Code Meaning
VALIDATION_ERROR Missing or invalid email/password
USER_EXISTS Email already registered
INVALID_CREDENTIALS Wrong email or password
INVALID_TOKEN Token is malformed or expired
UNAUTHORIZED No valid token provided
USER_NOT_FOUND Token valid but user no longer exists
SESSION_NOT_FOUND Refresh token revoked or session expired

Framework Integrations

FastAPI

pip install auth101[fastapi] sqlalchemy
from fastapi import Depends, FastAPI
from auth101 import Auth101
from auth101.adapters import SQLAlchemyAdapter

adapter = SQLAlchemyAdapter("sqlite:///auth.db")
auth = Auth101(secret="change-me-in-production", database=adapter)

app = FastAPI()

# Mount auth endpoints: POST /auth/sign-up/email, /auth/sign-in/email,
#                       POST /auth/refresh, POST /auth/revoke,
#                       POST /auth/sign-out, GET /auth/session
app.include_router(auth.fastapi_router(), prefix="/auth", tags=["auth"])

# Dependency that resolves to the authenticated User or raises 401
CurrentUser = auth.fastapi_current_user()

@app.get("/me")
async def me(user=Depends(CurrentUser)):
    return {"id": user.id, "name": user.name, "email": user.email}

Flask

pip install auth101[flask] sqlalchemy
from flask import Flask, g, jsonify
from auth101 import Auth101
from auth101.adapters import SQLAlchemyAdapter

adapter = SQLAlchemyAdapter("sqlite:///auth.db")
auth = Auth101(secret="change-me-in-production", database=adapter)

app = Flask(__name__)

# Mount auth endpoints under /auth
app.register_blueprint(auth.flask_blueprint(), url_prefix="/auth")

# Decorator that sets g.auth_user or returns 401
login_required = auth.flask_login_required()

@app.get("/me")
@login_required
def me():
    return jsonify({"id": g.auth_user.id, "name": g.auth_user.name, "email": g.auth_user.email})

Django

pip install auth101[django]

Option A – Zero-config (use auth101's default tables)

1. Add the auth101 Django app (settings.py):

INSTALLED_APPS = [
    ...
    "auth101.contrib.django",
]

2. Run migrations: python manage.py makemigrations && python manage.py migrate

3. Configure auth101 (myapp/auth.py):

from django.conf import settings
from auth101 import Auth101
from auth101.adapters import DjangoAdapter
from auth101.contrib.django.models import Auth101User

auth = Auth101(
    secret=settings.SECRET_KEY,
    database=DjangoAdapter(Auth101User),  # session, account, verification use defaults
)
Auth101Middleware = auth.get_django_middleware()
login_required = auth.django_login_required()

Option B – Custom user model only

If you have your own User model (with fields: id, name, email, email_verified, image, is_active, created_at, updated_at), pass it and omit session/account/verification; auth101 uses its default tables for those:

from myapp.models import MyUser
auth = Auth101(secret=..., database=DjangoAdapter(MyUser))

Option C – Custom models for all tables

Pass session_model, account_model, and/or verification_model to DjangoAdapter(...) to use your own tables. Alternatively, pass session, account, or verification to Auth101(...) with {"model": YourModel}.

4. Add middleware (settings.py):

MIDDLEWARE = [
    "myapp.auth.Auth101Middleware",   # sets request.auth_user on every request
    ...
]

5. Mount URLs (urls.py):

from django.urls import include, path
from myapp.auth import auth

urlpatterns = [
    path("auth/", include(auth.django_urls())),
    ...
]

6. Protect views (myapp/views.py):

from django.http import JsonResponse
from myapp.auth import login_required

@login_required
def profile(request):
    return JsonResponse({"id": request.auth_user.id, "name": request.auth_user.name, "email": request.auth_user.email})

Auth Endpoints

All framework integrations expose the same endpoints (relative to the mount prefix):

Method Path Body / Header Response
POST /sign-up/email {"email": "...", "password": "..."} {"user": {...}, "access_token": "...", "refresh_token": "...", "expires_in": N}
POST /sign-in/email {"email": "...", "password": "..."} {"user": {...}, "access_token": "...", "refresh_token": "...", "expires_in": N}
POST /refresh {"refresh_token": "..."} {"user": {...}, "access_token": "...", "refresh_token": "...", "expires_in": N}
POST /revoke {"refresh_token": "..."} {"success": true}
POST /sign-out Authorization: Bearer <access_token> {"success": true}
GET /session Authorization: Bearer <access_token> {"user": {...}}

In all responses, user has: id, name, email, email_verified, image, is_active.

Database schema

auth101 uses four tables (default names: users, account, session, verification). Column names can be customized via entity config fields mapping.

  • user – id, name, email, email_verified, image, is_active, created_at, updated_at. No password; credentials live in the account table.
  • account – id, account_id, provider_id, user_id, password (for credential provider), optional OAuth fields, created_at, updated_at. One row per (user, provider); email/password uses provider_id="credential".
  • session – id, token (stores refresh-token jti), expires_at, user_id, ip_address, user_agent, created_at, updated_at. Used for refresh-token revocation and rotation.
  • verification – id, identifier, value, expires_at, created_at, updated_at (for email verification / password reset; table is ready, flows are optional).

With SQLAlchemy you pass one adapter; auth101 migrate creates tables for any entity not customized in config. With Django you can use auth101’s default models (auth101.contrib.django) or pass your own to DjangoAdapter or via Auth101's entity config.

Database Persistence

SQLAlchemy (one adapter, four tables)

Pass a SQLAlchemyAdapter with a URL or engine. Run auth101 migrate --config myapp.auth_config to create the user, account, session, and verification tables (for any entity not customized in your Auth101 config). Table and column names are configured on Auth101, not on the adapter.

from auth101.adapters import SQLAlchemyAdapter

auth = Auth101(
    secret="...",
    database=SQLAlchemyAdapter("postgresql://user:pass@localhost/mydb"),
)

Works with any SQLAlchemy-supported database: PostgreSQL, MySQL, SQLite, etc.

Django ORM

Pass a DjangoAdapter with your user model. Session/account/verification are optional (auth101 uses default tables from auth101.contrib.django if omitted):

from auth101.adapters import DjangoAdapter
from auth101.contrib.django.models import Auth101User

auth = Auth101(
    secret=settings.SECRET_KEY,
    database=DjangoAdapter(Auth101User),
)

Testing

Use SQLite in-memory so each run is isolated; tables are created on first use, no migrate needed:

from auth101 import Auth101
from auth101.adapters import SQLAlchemyAdapter

auth = Auth101(
    secret="test-secret",
    database=SQLAlchemyAdapter("sqlite:///:memory:"),
)
# sign_up, sign_in, get_session, refresh, etc. work as in production

Data is lost when the process exits, which is ideal for unit tests.

License

MIT

Metadata

Release files for auth101 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for auth101 0.3.1
File Size Uploaded Provenance
auth101-0.3.1.tar.gz 37.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for auth101 0.3.1
File Interpreter ABI Platform
auth101-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 78.5 kB

Release files / auth101-0.3.1.tar.gz

Download URL auth101-0.3.1.tar.gz
Size 37.6 kB
Tags Source
SHA-256 checksum
How to use checksums
03a10fdb5e5871145a8383e0e88ceda3b13c64eb8083e5488652d264aa1e916a
BLAKE2b-256 checksum
How to use checksums
f562f4b26f2de6bc1bbdde39fa13876f7b77a6df4083716f51fb12279a8ea058
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

Release files / auth101-0.3.1-py3-none-any.whl

Download URL auth101-0.3.1-py3-none-any.whl
Size 40.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b03d800549240c18ea2b6132753420ecc5beeb6f94b1f318dcc551c3ed7db9cd
BLAKE2b-256 checksum
How to use checksums
5bfcaf0607e37e97023333ec71cd99ed6c4c1678386ad7d7f6483b1d8289a0de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

Security for auth101 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page