Skip to main content

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

Project description

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 / portsfeatures (use cases) → adapters and factoryfacadeintegrations. 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

auth101-0.3.1.tar.gz (37.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

auth101-0.3.1-py3-none-any.whl (40.9 kB view details)

Uploaded Python 3

File details

Details for the file auth101-0.3.1.tar.gz.

File metadata

  • Download URL: auth101-0.3.1.tar.gz
  • Upload date:
  • Size: 37.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for auth101-0.3.1.tar.gz
Algorithm Hash digest
SHA256 03a10fdb5e5871145a8383e0e88ceda3b13c64eb8083e5488652d264aa1e916a
MD5 b1b76e98357ea1d8edc7d48bdd7a1e83
BLAKE2b-256 f562f4b26f2de6bc1bbdde39fa13876f7b77a6df4083716f51fb12279a8ea058

See more details on using hashes here.

File details

Details for the file auth101-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: auth101-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 40.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for auth101-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b03d800549240c18ea2b6132753420ecc5beeb6f94b1f318dcc551c3ed7db9cd
MD5 3be31064090004c76e37d76b3e4b139d
BLAKE2b-256 5bfcaf0607e37e97023333ec71cd99ed6c4c1678386ad7d7f6483b1d8289a0de

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page