Production-ready starter with two auth paths:
- OIDC (Microsoft Entra ID) in the frontend using MSAL
- Local username/password in the backend (JWT)
It includes a unified User model in the React AuthContext, RBAC helpers, and optional backend protection using Entra access tokens.
- Dual auth (OIDC + Local)
- Unified user shape in
AuthContext - RBAC via
<RoleGate allowedRoles={['Admin']} /> - Token inspector for debugging
- Vite + FastAPI dev flow
- React 18 + TypeScript + Vite
- Tailwind CSS
- MSAL (
@azure/msal-browser,@azure/msal-react)
- FastAPI
- SQLAlchemy + SQLite
- JWT for local auth (
python-jose,passlib[bcrypt])
cd backend
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtCreate a .env in backend/:
SECRET_KEY=change_me
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
# Required only if validating OIDC access tokens
OIDC_ISSUER=https://login.microsoftonline.com/<TENANT_ID>/v2.0,https://sts.windows.net/<TENANT_ID>/
OIDC_AUDIENCE=<API-CLIENT-ID-or-App-ID-URI>
OIDC_JWKS_URL=https://login.microsoftonline.com/<TENANT_ID>/discovery/v2.0/keys
# OIDC policy (minimal)
OIDC_REQUIRED_SCOPES=access_as_user
OIDC_JWKS_CACHE_TTL_SECONDS=3600
# Dev-only setup endpoint
ENABLE_DEV_SETUP=true
# CORS
CORS_ALLOWED_ORIGINS=https://your-frontend.example.comStart the API:
uvicorn app.main:app --reloadcd frontend
npm install
cp .env.example .envEdit frontend/.env:
VITE_ENTRA_CLIENT_ID=<SPA-CLIENT-ID>
VITE_ENTRA_TENANT_ID=<TENANT-ID>
# Optional, required only if calling backend with OIDC access tokens
VITE_ENTRA_API_SCOPE=api://<API-CLIENT-ID>/access_as_userStart the UI:
npm run devSeed a default local admin:
curl -X POST http://localhost:8000/setupLogin with:
- username:
raffi - password:
password123
Use Sign in with Microsoft. MSAL handles login and user identity in the UI.
If you want to protect API endpoints with Entra tokens:
- Create an API App Registration and expose a scope (e.g.
access_as_user). - Set
VITE_ENTRA_API_SCOPEin the frontend. - Set
OIDC_ISSUER,OIDC_AUDIENCE,OIDC_JWKS_URLin the backend.
Then call protected endpoints with Authorization: Bearer <access_token>.
/
├── backend/
│ ├── app/
│ │ ├── auth/ # Hybrid Auth orchestration
│ │ │ ├── __init__.py # Hybrid auth logic
│ │ │ ├── local_jwt.py # Local JWT management
│ │ │ └── oidc.py # OIDC/Entra ID validation
│ │ ├── routers/ # API Endpoints
│ │ │ ├── local_auth.py # /token, /me
│ │ │ ├── oidc_auth.py # /me/oidc
│ │ │ └── api.py # /users, /status
│ │ ├── db.py # Database configuration
│ │ ├── models.py # SQLAlchemy models
│ │ ├── schemas.py # Pydantic schemas
│ │ ├── seed.py # Database seeding
│ │ └── main.py # FastAPI app entry
│ └── sql_app.db # SQLite database
├── frontend/
│ ├── src/
│ │ ├── auth/ # MSAL config + claims helpers
│ │ ├── context/ # AuthContext (unified user)
│ │ ├── components/ # RoleGate, TokenInspector, UI blocks
│ │ ├── pages/ # LoginPage, HomePage
│ │ └── App.tsx # Router + auth guards
└── README.md- Keep
SECRET_KEYsecret and rotate in production. - Local JWTs are stored in
localStorage(simple dev flow). For higher security, consider HttpOnly cookies.
- Set
CORS_ALLOWED_ORIGINSto your real frontend domains. - Require scopes via
OIDC_REQUIRED_SCOPES. - Use API scopes (
VITE_ENTRA_API_SCOPE) and access tokens for backend protection. - Disable dev setup endpoint (
ENABLE_DEV_SETUP=false). - Add database migration for
is_disabledand manage account lockouts.
MIT