Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AllSafe Fast

Production-grade authentication framework for FastAPI. Async-first, provider-based, plugin-extensible, and secure by default.

Features

  • Async-first: Built on FastAPI, SQLAlchemy async, and async Redis. Every storage operation is async.
  • Provider-based auth: Password, Generic OAuth, Generic OIDC, Magic Link, Email OTP, and custom providers behind a uniform AuthProvider protocol.
  • Plugin extensibility: 2FA, Organizations, API Keys, and more behind an AllSafePlugin protocol with per-plugin routers, schemas, hooks, and table metadata.
  • Storage abstraction: Core services depend on typing.Protocol interfaces — the core never imports SQLAlchemy. Swap Postgres for memory or any backend without touching auth logic.
  • Fast path: JWT verification in auth.user() decodes and validates the token in memory with no database and no Redis queries on the hot request path.
  • Secure by default:
    • Argon2id password hashing (time_cost=3, memory_cost=65536 KiB, parallelism=4).
    • Refresh-token rotation with reuse detection (token-theft defense).
    • Tokens (refresh, verification, OTP, magic-link) stored only as SHA-256 hashes.
    • HttpOnly, Secure, SameSite cookies with safe defaults.
    • Account-enumeration protection on forgot-password.
    • OAuth state parameter + PKCE enforced.
    • CSRF header validation for cookie-based auth.

Installation

pip install allsafe-fast

Quick start

from fastapi import FastAPI
from allsafe_fast import Auth

app = FastAPI()
auth = Auth()  # reads ALLSAFE_* env vars

app.include_router(auth.router, prefix="/auth")

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

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

Configuration

All configuration is read from environment variables prefixed with ALLSAFE_.

Variable Default Description
ALLSAFE_DATABASE_URL SQLAlchemy async URL, e.g. postgresql+asyncpg://user:pass@host/db
ALLSAFE_REDIS_URL Redis URL, e.g. redis://localhost:6379/0
ALLSAFE_SECRET_KEY JWT signing key (min 32 chars; required in production)
ALLSAFE_ACCESS_TOKEN_EXPIRE 900 Access token lifetime in seconds (15 min)
ALLSAFE_REFRESH_TOKEN_EXPIRE 2592000 Refresh token lifetime in seconds (30 days)
ALLSAFE_COOKIE_SECURE true Set Secure on auth cookies
ALLSAFE_COOKIE_HTTP_ONLY true Set HttpOnly on auth cookies
ALLSAFE_COOKIE_SAME_SITE lax Cookie SameSite policy
ALLSAFE_RATE_LIMIT 5 Requests per window per identifier
ALLSAFE_RATE_LIMIT_WINDOW 900 Rate-limit window in seconds
ALLSAFE_ISSUER allsafe_fast JWT iss claim
ALLSAFE_AUDIENCE application JWT aud claim
ALLSAFE_ENV development development or production

Copy .env.example to .env and fill in values, or run:

allsafe init

Architecture

src/allsafe_fast/
├── auth.py              # Auth facade + user() dependency factory
├── config.py            # AuthConfig + pydantic-settings
├── exceptions.py        # Exception hierarchy
├── types.py             # UserPrincipal, AuthResult, TokenPair, ...
├── core/                # IdentityService, AccountService, SessionManager,
│                        #   AuthenticationEngine, AuthorizationService,
│                        #   VerificationService, HookRegistry
├── models/              # Plain dataclasses: User, Account, Session, Verification
├── storage/             # Protocols + memory + sqlalchemy adapters
├── security/            # passwords, jwt, tokens, hashing, cookies, secrets
├── providers/           # password, oauth, oidc, magic_link, email_otp
├── plugins/             # plugin protocol + registry
├── api/                 # router, dependencies, context, schemas
├── rate_limit/          # protocols + redis + memory limiters
├── redis/               # client, key builders, state manager
└── services/            # email service + hooks re-export

The fast path

auth.user() returns a FastAPI dependency that:

  1. Reads the JWT from the Authorization: Bearer … header (or allsafe_session cookie).
  2. Verifies signature, exp, iss, aud, and sub in memory.
  3. Constructs a UserPrincipal directly from the claims.

No database query. No Redis query. That is the hot path for authenticated requests.

Storage abstraction

Core services (core/*) depend only on UserStoreProtocol, AccountStoreProtocol, SessionStoreProtocol, and VerificationStoreProtocol from storage/protocols.py. The SQLAlchemy adapter implements them; an in-memory adapter is provided for tests. You can write your own adapter for any backend.

Refresh-token rotation

When a refresh token is used:

  1. The stored SHA-256 hash is looked up.
  2. If found and not revoked, the old session is revoked and a brand-new access/refresh pair is issued.
  3. If a revoked refresh token is presented again, this is treated as token theft — all of the user's sessions are revoked immediately.

Account linking policy

AllSafe never auto-merges accounts on email match alone. A new OAuth identity with an email that matches an existing user creates a pending account that must be explicitly linked by the authenticated user, preventing account takeover via a rogue OAuth provider.

CLI

allsafe init            # write .env with a generated secret
allsafe secret          # print a fresh secret key
allsafe db generate     # print SQL DDL for all tables
allsafe db migrate      # run alembic migrations
allsafe doctor          # check config, DB, and Redis connectivity

License

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages