David Boyd giteaadmin
  • Joined on 2025-07-14

azure-sso (0.2.0)

Published 2026-07-02 16:08:24 +00:00 by giteaadmin

Installation

pip install --index-url  azure-sso

About 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

Requirements

Requires Python: >=3.10
Details
PyPI
2026-07-02 16:08:24 +00:00
5
DOES / dboyd
Proprietary
56 KiB
Assets (2)
Versions (2) View all
0.2.0 2026-07-02
0.1.0 2026-07-02