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 uniquejti), 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 includetable_name(ormodel_name) and optionallyfields(auth101 field → your column). Auth101 never creates or migrates this table; we only use it via the mapping. If bothuseranduser_field_mappingare provided,userwins.
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_mappingwithtable_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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03a10fdb5e5871145a8383e0e88ceda3b13c64eb8083e5488652d264aa1e916a
|
|
| MD5 |
b1b76e98357ea1d8edc7d48bdd7a1e83
|
|
| BLAKE2b-256 |
f562f4b26f2de6bc1bbdde39fa13876f7b77a6df4083716f51fb12279a8ea058
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b03d800549240c18ea2b6132753420ecc5beeb6f94b1f318dcc551c3ed7db9cd
|
|
| MD5 |
3be31064090004c76e37d76b3e4b139d
|
|
| BLAKE2b-256 |
5bfcaf0607e37e97023333ec71cd99ed6c4c1678386ad7d7f6483b1d8289a0de
|