{"id":51101735,"url":"https://github.com/benavlabs/crudauth","last_synced_at":"2026-06-24T11:30:27.675Z","repository":{"id":365171206,"uuid":"1262420077","full_name":"benavlabs/crudauth","owner":"benavlabs","description":"Batteries-included, transport-agnostic authentication for FastAPI.","archived":false,"fork":false,"pushed_at":"2026-06-16T06:18:44.000Z","size":2222,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T08:18:06.353Z","etag":null,"topics":["auth","authentication","backend","fastapi","security","sqlalchemy"],"latest_commit_sha":null,"homepage":"https://benavlabs.github.io/crudauth/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benavlabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-08T01:07:32.000Z","updated_at":"2026-06-16T06:18:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/benavlabs/crudauth","commit_stats":null,"previous_names":["benavlabs/crudauth"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/benavlabs/crudauth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benavlabs%2Fcrudauth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benavlabs%2Fcrudauth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benavlabs%2Fcrudauth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benavlabs%2Fcrudauth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benavlabs","download_url":"https://codeload.github.com/benavlabs/crudauth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benavlabs%2Fcrudauth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34731243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth","authentication","backend","fastapi","security","sqlalchemy"],"created_at":"2026-06-24T11:30:27.132Z","updated_at":"2026-06-24T11:30:27.659Z","avatar_url":"https://github.com/benavlabs.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://benavlabs.github.io/crudauth\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/crudauth-cover-dark.png\"\u003e\n      \u003cimg src=\"docs/assets/crudauth-cover-light.png\" alt=\"crudauth\" width=\"50%\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\" markdown=1\u003e\n  \u003ci\u003e\u003cb\u003eBatteries-included, transport-agnostic authentication for FastAPI.\u003c/b\u003e\u003c/i\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://pypi.org/project/crudauth/\"\u003e\n  \u003cimg src=\"https://img.shields.io/pypi/v/crudauth?color=%2334D058\u0026label=pypi%20package\" alt=\"PyPi Version\"/\u003e\n\u003c/a\u003e\n\u003ca href=\"https://pypi.org/project/crudauth/\"\u003e\n  \u003cimg src=\"https://img.shields.io/pypi/pyversions/crudauth.svg?color=%2334D058\" alt=\"Supported Python Versions\"/\u003e\n\u003c/a\u003e\n\u003ca href=\"https://github.com/benavlabs/crudauth/blob/main/LICENSE\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-34D058\" alt=\"License\"/\u003e\n\u003c/a\u003e\n\u003ca href=\"https://benavlabs.github.io/crudauth\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/docs-benavlabs.github.io%2Fcrudauth-34D058?logo=materialformkdocs\u0026logoColor=white\" alt=\"Documentation\"/\u003e\n\u003c/a\u003e\n\u003ca href=\"https://deepwiki.com/benavlabs/crudauth\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/DeepWiki-1F2937.svg?logo=book\u0026logoColor=white\u0026labelColor=1F2937\u0026color=34D058\" alt=\"DeepWiki\"/\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://benavlabs.github.io/crudauth\"\u003eDocs\u003c/a\u003e · \u003ca href=\"https://deepwiki.com/benavlabs/crudauth\"\u003eDeepWiki\u003c/a\u003e · \u003ca href=\"https://discord.com/invite/TEmPs22gqB\"\u003eDiscord\u003c/a\u003e\n\u003c/p\u003e\n\n\u003chr\u003e\n\u003cp align=\"justify\"\u003e\n\u003cb\u003ecrudauth\u003c/b\u003e gives you one \u003ccode\u003eCRUDAuth\u003c/code\u003e object that wires cookie sessions, JWT bearer tokens, OAuth, and email flows (verify / reset / change) - with CSRF, escalating login lockout, sudo mode, and multi-device session management - over \u003cb\u003eyour own\u003c/b\u003e SQLAlchemy \u003ccode\u003eUser\u003c/code\u003e model. App policy lives in hooks, not in forked dependency code. Sessions and bearer both resolve to the same \u003ccode\u003ePrincipal\u003c/code\u003e, so narrowing or adding a transport never changes how you authorize a route.\n\u003c/p\u003e\n\n\u003chr\u003e\n\n## Features\n\n- **Transport-agnostic**: cookie sessions and JWT bearer tokens behind a single `Principal`; first credential present wins, and authorization code never depends on *which* transport authenticated.\n- **Your model, your schema**: works over your existing SQLAlchemy `User` via a logical-field `column_map` - no forced renames, no second user table.\n- **Secure by default**: synchronizer-token CSRF, escalating per-IP/per-user login lockout, bcrypt with SHA-256 pre-hash (no 72-byte truncation), timing-equalized login, and trusted-proxy IP resolution.\n- **OAuth**: Google, GitHub, or a custom provider - with the `state` bound to the initiating browser to block login CSRF.\n- **Email flows**: verify / reset / change - you implement the `EmailSender` port (render your own HTML from `context.link`), the package mints and verifies the signed, single-use tokens.\n- **Sudo mode**: short-lived re-authentication to gate sensitive actions, stamped on the session and cleared on logout.\n- **Multi-device sessions**: list, revoke one, or \"sign out everywhere\", with a configurable per-user session cap.\n- **App policy in hooks**: `AuthHooks` for welcome email, trial grant, audit logging - fired uniformly across every auth path.\n- **Pluggable backends**: in-memory for dev, Redis for production - for sessions, CSRF, lockout counters, and one-time tokens.\n- **Fully typed \u0026 async**: ships `py.typed`, built on SQLAlchemy 2.0 and Pydantic v2.\n\n## Requirements\n\n- **Python** 3.10+\n- **FastAPI**, **SQLAlchemy 2.0+**, **Pydantic v2** (installed as dependencies)\n\n## Install\n\n```bash\npip install crudauth            # core (session + bearer)\npip install \"crudauth[all]\"     # + httpx (oauth), redis, user-agents\n```\n\nOr with uv:\n\n```bash\nuv add crudauth\n```\n\n## AI agents (library skill)\n\ncrudauth ships a [library skill](https://library-skills.io) embedded in the package, so AI coding\nagents follow its actual conventions and gotchas (account shapes, gates, recovery, custom email\nbodies, production wiring) in sync with the version you installed. After adding crudauth, install it\ninto your project:\n\n```bash\nuvx library-skills            # scans deps, links bundled skills (re-run to keep them in sync on upgrade)\nuvx library-skills --claude   # for Claude Code (.claude/skills)\n```\n\n## Quickstart\n\nSessions are the default - no `transports=` needed. You get cookie auth, CSRF,\nlogin lockout, secure cookies, and `/login` `/logout` `/register` `/me`.\n\n```python\nfrom fastapi import FastAPI, Depends\nfrom crudauth import CRUDAuth, Principal\nfrom myapp.db import get_session\nfrom myapp.models import User\n\nauth = CRUDAuth(session=get_session, user_model=User, SECRET_KEY=\"change-me\")\n\napp = FastAPI()\napp.include_router(auth.router)\n\n@app.get(\"/dashboard\")\nasync def dashboard(me: Principal = Depends(auth.current_user())):\n    return {\"hello\": me.user.username}\n```\n\n## The user model\n\nInherit the mixin and get every column the package needs; your own columns\ncoexist freely.\n\n```python\nfrom sqlalchemy.orm import Mapped, mapped_column\nfrom crudauth.models import AuthUserMixin\nfrom myapp.db import Base\n\nclass User(Base, AuthUserMixin):\n    __tablename__ = \"users\"\n    full_name: Mapped[str | None] = mapped_column(default=None)\n```\n\nExisting table with different names? Map the contract, don't rename your schema:\n\n```python\nauth = CRUDAuth(\n    session=get_session, user_model=LegacyAccount, SECRET_KEY=...,\n    column_map={\"id\": \"account_id\", \"email\": \"email_address\", \"hashed_password\": \"pw_hash\"},\n)\n```\n\n## Protecting routes - one factory, every case a kwarg\n\n```python\nauth.current_user()                              # required, 401 if anon\nauth.current_user(optional=True)                 # None instead of raising\nauth.current_user(superuser=True)                # 403 unless is_superuser\nauth.current_user(verified=True)                 # 403 unless email_verified\nauth.current_user(scopes=[\"reports:read\"])       # 403 unless scopes ⊇ required\nauth.current_user(transport=\"bearer\")            # narrow to one transport\nauth.current_user(superuser=True, check=my_predicate)  # extra per-route check\n```\n\nThe resolved `Principal` carries `user_id`, `is_superuser`, `scopes`,\n`transport` (which transport authed the request), and `user` (your resolved\nrow).\n\n## Multiple transports, one identity\n\n```python\nfrom crudauth import CRUDAuth, SessionTransport, BearerTransport\n\nauth = CRUDAuth(\n    session=get_session, user_model=User, SECRET_KEY=...,\n    transports=[\n        SessionTransport(backend=\"redis\", redis_url=..., csrf=True),  # browsers\n        BearerTransport(access_ttl=900, refresh=\"cookie\"),            # apps/scripts\n    ],\n)\n```\n\nWhen both credentials are present, the **first transport in the list wins**.\nCSRF is a property of the session transport - it appears only where sessions do,\nnever on bearer/api-key paths.\n\n## Storage \u0026 lifespan\n\nServer-side backends open connections on startup - call `initialize()` /\n`shutdown()` in your lifespan:\n\n```python\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await auth.initialize()\n    yield\n    await auth.shutdown()\n```\n\n## OAuth, email, hooks, sudo\n\nSee the usage cookbook for OAuth (Google / GitHub / custom providers), email\nflows (implement the `EmailSender` port; the package mints/verifies the signed\ntokens), lifecycle hooks (`AuthHooks` - welcome email, trial grant, audit log),\nsudo mode (`sudo=SudoConfig()` + `auth.require_sudo()`), and dropping to\nprimitives.\n\n## Architecture\n\ncrudauth is ports-and-adapters with feature slices and a single composition\nroot (`CRUDAuth`). The layering and the import-direction rules live in the\n[Architecture docs](https://benavlabs.github.io/crudauth/architecture/) - read them before\nadding a transport, OAuth provider, or storage backend; each is meant to be a drop-in\nfile, not a cross-cutting edit.\n\n## License\n\n[`MIT`](LICENSE)\n\n## Contact\n\nBenav Labs – [benav.io](https://benav.io), [Discord](https://discord.com/invite/TEmPs22gqB)\n\n## The Benav Labs FastAPI family\n\ncrudauth is part of a family of composable FastAPI building blocks - use whichever you need:\n\n- **[FastCRUD](https://github.com/benavlabs/fastcrud)** - powerful CRUD methods and automatic endpoint creation for your SQLAlchemy models.\n- **[CRUDAdmin](https://github.com/benavlabs/crudadmin)** - a modern, secure admin interface generated straight from your models.\n- **[Fastro (FastAPI-boilerplate)](https://github.com/benavlabs/FastAPI-boilerplate)** - a batteries-included FastAPI starter: auth, CRUD, jobs, caching, and rate-limits.\n- **[FastroAI](https://fastro.ai)** - the complete FastAPI SaaS template: payments, entitlements, email, a frontend, and AI agents.\n\n## Build a full SaaS on FastAPI\n\ncrudauth handles authentication in **[FastroAI](https://fastro.ai)** - the complete FastAPI SaaS template: auth, Stripe payments (subscriptions, credits, discounts), entitlements, transactional email, an Astro frontend, and PydanticAI agents, wired together and production-ready.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://fastro.ai\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/benavlabs/FastAPI-boilerplate/main/docs/assets/fastroai-card-dark.png\"\u003e\n      \u003cimg src=\"https://raw.githubusercontent.com/benavlabs/FastAPI-boilerplate/main/docs/assets/fastroai-card-light.png\" alt=\"FastroAI - the complete FastAPI SaaS template: auth, Stripe payments, entitlements, email, frontend and AI\" width=\"100%\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cb\u003e\u003ca href=\"https://fastro.ai\"\u003eShip your SaaS faster with FastroAI →\u003c/a\u003e\u003c/b\u003e\u003c/p\u003e\n\n\u003chr\u003e\n\u003ca href=\"https://benav.io\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/benavlabs/FastAPI-boilerplate/main/docs/assets/benav-labs-banner-dark.png\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/benavlabs/FastAPI-boilerplate/main/docs/assets/benav-labs-banner-light.png\" alt=\"Benav Labs - benav.io\" width=\"100%\"/\u003e\n  \u003c/picture\u003e\n\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenavlabs%2Fcrudauth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenavlabs%2Fcrudauth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenavlabs%2Fcrudauth/lists"}