azure-sso (0.1.0)
Installation
pip install --index-url azure-ssoAbout this package
Reusable Azure AD (Entra ID) OIDC + local break-glass login library with RBAC and audit logging. Framework-agnostic core with Flask and FastAPI adapters.
azure-sso (azsso)
A small, reusable Python user-login library for DOES web apps. It packages the three login methods proven in the does-catalog blueprint so any app — starting with the addverify GUI — can wrap them cleanly instead of re-implementing auth each time.
| Login method | What it is |
|---|---|
| Azure AD (Entra ID) OIDC | MSAL confidential-client auth-code flow. Auto-provision on first sign-in, or pre-provision a role beforehand. |
| Local break-glass | Username + password (PBKDF2-SHA256), flagged in the audit log. Works when AD is unavailable or not yet configured. |
| RBAC + audit | Configurable role hierarchy (viewer < analyst < admin by default) and an append-only audit_log covering every auth and admin action. |
Design
┌──────────────────────────────┐
│ AuthService │ ← framework-agnostic core
│ (all login + admin logic) │ imports NO web framework
└───────────────┬──────────────┘
┌─────────────────────┼─────────────────────┐
┌────┴─────┐ ┌───────┴───────┐ ┌───────┴────────┐
│ MsalAuth │ │ UserStore │ │ security │
│ (OIDC) │ │ (pluggable) │ │ (pbkdf2 hash) │
└──────────┘ └───────┬───────┘ └────────────────┘
┌──────────┴──────────┐
┌─────┴──────┐ ┌───────┴────────┐
│ SQLite │ │ PostgreSQL │
└────────────┘ └────────────────┘
Adapters (thin shells over AuthService):
• azsso.adapters.flask — blueprint + flask-login (matches does-catalog UX)
• azsso.adapters.fastapi — router + session auth
Two design choices you asked for:
- Both frameworks. The login logic lives once in
AuthService; Flask and FastAPI are thin adapters. No duplicated auth code. - Its own, movable database. The user/audit store has a dedicated
AUTH_DB_URL, separate from the host app's main DB. Point it at a standalone database on today's Postgres instance; move it to another server later by changing one env var — no code change. SQLite is the zero-infra dev default.
Install
pip install -e '.[flask-all]' # Flask + flask-login + psycopg2
pip install -e '.[fastapi-all]' # FastAPI + uvicorn + psycopg2
pip install -e '.[dev]' # tests only (SQLite, no web framework)
Configure
Copy .env.example → .env. Minimum for local dev:
SECRET_KEY=some-long-random-string
AUTH_DB_URL=sqlite:///azsso_auth.db
Add the four AZURE_* vars to light up "Sign in with Microsoft"; leave them out
and that button is hidden while local login keeps working.
Use it — Flask
from flask import Flask
from flask_login import login_required, current_user
from azsso.adapters.flask import init_auth, require_role
app = Flask(__name__)
app.secret_key = ...
init_auth(app, url_prefix="/auth") # registers all /auth/* routes
@app.route("/")
@login_required
def index():
return f"hi {current_user.username}"
@app.route("/admin")
@require_role("admin")
def admin():
...
Registers: /auth/login, /auth/login/ad, /auth/login/callback,
/auth/login/local, /auth/logout, and the /auth/admin/users management
screens. Bundled default templates render out of the box; define your own
login.html / admin_users.html / admin_user_edit.html to override.
Use it — FastAPI
from fastapi import FastAPI, Depends
from azsso.adapters.fastapi import init_auth, require_role
from azsso.models import User
app = FastAPI()
init_auth(app, prefix="/auth", secret_key=...)
@app.get("/")
def index(user: User = Depends(require_role("viewer"))):
return {"me": user.username}
Use it — no framework
from azsso import AuthService
svc = AuthService() # config + store from env
svc.create_local_user("admin", "Admin", "admin", actor="bootstrap") # -> temp pw
svc.local_login("admin", "<temp pw>") # -> LoginResult
svc.provision_ad_user("jane@dc.gov", "Jane", "analyst", actor="admin")
Bootstrap the first admin
There is no seeded account (by design). Create one once:
python -c "from azsso import AuthService; \
print(AuthService().create_local_user('admin','Admin','admin',actor='bootstrap').temp_password)"
Copy the printed temporary password, sign in at /auth/login, then create AD or
additional accounts from the Users screen.
Azure AD app registration
Full walkthrough: docs/azure-setup.md — per-app
registration steps, the exact permissions required (runtime vs. setup-time),
local-dev redirects, first-admin bootstrap, and troubleshooting.
Quick version:
az ad app create --display-name "addverify-webapp" \
--sign-in-audience AzureADMyOrg \
--web-redirect-uris "https://addverify.doesworks.net/auth/login/callback"
az ad app credential reset --id <appId> --display-name "addverify-secret"
Tenant for all DC.gov apps: 8fe449f1-8b94-4fb7-9906-6f939da82d73. No Graph API
permissions or admin consent are needed (see the doc's Permissions section).
Test
pip install -e '.[dev]' && python -m pytest -q
Core tests run on SQLite with no web framework; adapter tests skip gracefully if Flask/FastAPI aren't installed.
Schema
users and audit_log match the does-catalog columns, so a Postgres store can
live beside the existing catalog schema in its own database.
users |
audit_log |
|---|---|
id, username (unique), email, display_name, password_hash, auth_source (ad/local), role, is_active, is_break_glass, created_by, created_at, last_login |
id, action, changed_by, ip_address, reason, target, created_at |