A reusable full-stack identity and access management template with authentication, OAuth2/PKCE integration, and fine-grained Policy-Based Access Control (PBAC). Every access decision is made by an assigned, active Policy; a user's role column is display/grouping metadata only and is never consulted when deciding what someone can do. Supports email+password and Google OAuth2 (with PKCE) login, fully async operations, and JWT authentication delivered as httpOnly cookies.
The product name shown in the UI and emails is configurable via the APP_NAME / VITE_APP_NAME environment variables, not hardcoded.
See docs/README.md for the full documentation set β architecture, authentication, authorization, database, API reference, background workers, security, testing, Docker, CI/CD, and deployment β and docs/template-usage.md for how to clone and customize this repo as a starting point for your own project.
This started as the same authentication/authorization foundation getting rebuilt from scratch for every startup take-home assignment that needed some combination of auth, OAuth2, and roles. It grew from a planned "small reusable module" into a full exploration of what auth actually involves β refresh rotation, rate limiting, background email delivery, and a real test suite β as each rebuild kept surfacing another problem worth solving properly instead of again. See Project Story for the full history.
- Backend: FastAPI (fully async), SQLAlchemy 2.0 (async,
asyncpg), Alembic migrations - Authentication: Email + Password (Argon2 hashing, JWT access & refresh tokens), Google OAuth2 with PKCE
- Authorization: Policy-Based Access Control (PBAC) β see PBAC Architecture
- Frontend: TypeScript, React 19 + Vite, Chakra UI v3
- State Management: Zustand (client/session state) + TanStack Query (server state/caching)
- Database: PostgreSQL (async)
- Caching & Tasks: Redis + Taskiq (async background email delivery)
- Deployment: Docker (dev and production Compose files)
- Authentication answers who is calling β email+password or Google OAuth2/PKCE, JWT access+refresh token pair delivered as httpOnly, secure,
SameSite=Strictcookies. See Authentication Overview and OAuth2 / PKCE. - Authorization answers what they're allowed to do β every protected route depends on
require_authorization(action, resource_type), which checks the caller's assigned, activePolicyrows (optionally condition-gated by time, network, ownership, and more). Nothing above that layer ever readsroleto make an access decision. See PBAC Architecture. - New accounts (signup or first-time OAuth2 login) are granted access via an explicit
self_servicepolicy assignment, not a default role. role(user/admin/system) still exists on theuserstable as display/grouping metadata, and the reservedsystemaccount is excluded from OAuth2 login and from generic admin routes as a resource-protection invariant β but no route decides access by comparingrole.
git clone https://github.com/Nachiket-2024/mystic-auth.git
cd mystic-authInstructions below assume that you are at the root of the repository while running the commands.
Install backend dependencies:
cd backend
pip install -r requirements.txtInstall frontend dependencies:
cd frontend
npm installAll environment variables are defined in .env.example in both the project root and the frontend folder. Copy each to .env and fill in your own values:
cp .env.example .env
cp frontend/.env.example frontend/.envSECRET_KEY must be at least 32 characters β the app refuses to start with a shorter value (see Security Decisions).
Instructions below assume that you are at the root of the repository while running the commands.
Configure your Google Cloud project and enable the OAuth API before use (see OAuth2 / PKCE for the exact
GOOGLE_REDIRECT_URIrequirement).
docker compose upOnce the services are running:
- Backend: http://localhost:8000/docs β FastAPI API docs and endpoints
- Frontend: http://localhost:5173 β React + Vite frontend
- PostgreSQL:
localhost:5433β Database ready for connections (non-default host port; containers reach it atpostgres:5432internally) - Redis:
localhost:6380β Cache, rate limiting, and Taskiq broker (non-default host port; containers reach it atredis:6379internally) - Taskiq worker: Automatically listens for async tasks (email sending)
- Alembic migrations: Run automatically on stack startup via the dedicated
alembicservice (alembic upgrade head); in production Compose,backend/taskiq_workeralso wait for it to complete before starting (see Docker Overview)
See Docker Overview for the full service breakdown and Deployment Guide for production Compose usage and free/low-cost hosting options.
Make sure PostgreSQL is running locally and the database exists. Redis can be run locally or via Docker.
cd backend
alembic upgrade headuvicorn backend.app.main:app --reload- Backend: http://localhost:8000/docs
- PostgreSQL:
localhost:5432 - Redis:
localhost:6379
taskiq worker backend.app.taskiq_tasks.email_tasks:broker --reloadcd frontend
npm run dev- Frontend: http://localhost:5173
After starting the app for the first time, create the reserved system account β a one-time step that seeds the account holding the system_superuser policy (see PBAC Policy Examples).
docker compose exec -it backend python -m app.scripts.create_system_usercd backend
python -m app.scripts.create_system_userYou will be prompted to enter a name, email, and password interactively:
--- System Superuser Creation ---
Enter system user name: Your Name
Enter system user email: you@example.com
Enter system user password:
System user 'you@example.com' created successfully.
This only needs to be run once. The system user persists in the database volume. It can never be created, modified, or promoted via any API endpoint β CLI only.
| Feature | Details |
|---|---|
| Signup | Creates an account and assigns the baseline self_service policy; sends an email verification link |
| Email Verification | Single-use, Redis-backed token |
| Login | Timing-attack-resistant password check; returns JWT access + refresh tokens as httpOnly cookies |
| Google OAuth2 (PKCE) | Creates or logs in a user; Google's own email verification is trusted, so no separate verification step is needed |
| Token Refresh | Rotates the refresh token; reuse of an already-rotated token revokes every session for that account |
| Logout | Revokes the current refresh token, clears cookies |
| Logout All | Revokes every refresh token for the account, across every device |
| Forgot Password | User requests a reset link via email (same generic response whether or not the email is registered) |
| Reset Password | User redeems the link, sets a new password (strength-validated, can't reuse the current password), and every other session is logged out |
See Authentication Overview for the full mechanics of each flow.
- Policy-Based Access Control β every action is gated by an assigned policy, never by
role - JWT access and refresh tokens stored as httpOnly, secure,
SameSite=Strictcookies - Refresh token rotation with reuse detection (a replayed token revokes every session for that account)
- Dual rate limiting (per-IP and per-account) plus a separate brute-force lockout on login
- Timing-attack-resistant login/signup/password-reset paths
- Email verification required before password-based login
- Password strength validation on signup and password reset, with same-password reuse prevention
- Security response headers (CSP, HSTS, X-Frame-Options, etc.) on every response
- Trusted-proxy-aware IP resolution (
TRUSTED_PROXY_IPS) for rate limiting, lockout, and audit logging behind a reverse proxy SECRET_KEYminimum-length enforcement at startup- Two independent audit logs: a security/session-event log and a PBAC decision log β see Database Design
- System user protected from deletion, role changes, and OAuth2 login via API β CLI-only creation
See Security Hardening and Security Decisions for the full detail and rationale, and Known Issues & Concerns for what's tracked as still outstanding.
- All credentials and secrets are loaded from
.env - Alembic is used for database migrations
- Redis + Taskiq are used for async email delivery, caching, and rate limiting
- OAuth2 setup requires Google Cloud credentials
- Zustand manages client-side session state; TanStack Query manages all server-state caching
- Type Safety: Full TypeScript support across the frontend (components, hooks, store)
- The system user can only be created via CLI β it is never exposed through any API endpoint
Full documentation lives in docs/, organized by feature/domain:
- Architecture (system overview, backend, frontend)
- Authentication & OAuth2/PKCE
- Authorization (PBAC)
- Database Design
- API Reference
- Background Workers
- Security
- Testing
- Docker
- CI/CD
- Deployment
- Known Issues & Concerns
This is an open-source template β issues and pull requests are welcome:
- Check the documentation first, especially Known Issues & Concerns and PBAC Troubleshooting, since your question may already be answered there.
- Search existing GitHub Issues before opening a new one.
- If you've found a bug, open a new Issue with clear reproduction steps (what you ran, what you expected, what happened instead).
- Fixes and improvements are welcome as Pull Requests.
This project is licensed under the MIT License - see the LICENSE file for details.







