A reference full-stack "hello world" that runs behind Stanford workgroup-based SAML authentication, deployable to AWS or GCP with Terraform.
- Frontend: Next.js / React (containerized)
- Backend: Python FastAPI (containerized)
- Database: SQLite (on a persistent disk)
- Auth: Shibboleth SP sidecar (Apache +
mod_shib) → Stanford IdP, with workgroup authorization viaeduPersonEntitlement
Docs at a glance:
- First-time deployment: pick your cloud —
docs/deployment-guide-aws.mdordocs/deployment-guide-gcp.md— each is a full, self-contained, step-by-step walkthrough from local dev through a live deployment. - Connecting Stanford SSO or managing access:
docs/stanford-saml-registration.mdcovers SPDB/Workgroup registration specifically. - Architecture, endpoints, or repo layout: keep reading below.
┌─────────────────────────────────────────┐
Browser ── HTTPS ────▶│ shibboleth (Apache + mod_shib, :443) │
(Stanford login) │ · terminates Stanford SAML │
│ · enforces workgroup at the proxy │
│ · injects trusted identity headers │
└───────────────┬───────────────┬─────────┘
│ / │ /api
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ frontend (Next.js│ │ backend (FastAPI) │
│ :3000) │ │ :8000) │
└──────────────────┘ └────────┬─────────┘
│
┌───────▼────────┐
│ SQLite /data │
│ (persistent disk)│
└─────────────────┘
The Shibboleth container passes X-Remote-User (SUNet ID) and X-Entitlement
(workgroups) to the apps after stripping any client-supplied copies, so the
backend can trust them. The backend re-checks workgroup membership on protected
routes; the proxy also enforces it on that same route (/api/hello) when
REQUIRED_WORKGROUP is set — every other route just requires a valid session.
frontend/ Next.js app + Dockerfile
backend/ FastAPI app (auth, SQLite) + Dockerfile
shibboleth/ Apache + mod_shib SP sidecar (shibboleth2.xml, attribute-map.xml)
dev/ Dev-only nginx that mocks the SP for local runs
deploy/ bootstrap.sh (cloud-agnostic first-boot provisioning)
terraform/aws Single EC2 VM + EIP + EBS data disk
terraform/gcp Single Compute Engine VM + static IP + persistent disk
docs/ AWS/GCP deployment guides + Stanford SPDB/Workgroup registration walkthrough
Requires Docker. A dev nginx injects mock identity headers so you can exercise the real auth/authz code path without a real IdP.
cp .env.example .env # optional; dev has sane defaults
docker compose -f docker-compose.dev.yml up --build
open http://localhost:8080You'll see the mock user, their workgroups, and an incrementing SQLite visit count. To test the 403 (non-member) path, run with a mismatched entitlement:
DEV_ENTITLEMENT="stanford:not-my-group" \
docker compose -f docker-compose.dev.yml up --build/api/hello will now deny access (the required workgroup isn't in the mock
entitlements) while /api/me still shows the user.
Prereqs: Terraform ≥ 1.5; AWS or GCP credentials; a DNS name you control.
cd terraform/aws # or terraform/gcp
cp terraform.tfvars.example terraform.tfvars
# edit: app_hostname, admin_ssh_public_keys, letsencrypt_email, required_workgroup, ...
terraform init
terraform applyThen follow the outputs:
- Point your
app_hostnameDNS A record at the outputpublic_ip. - Re-apply with
enable_letsencrypt = truefor a real TLS cert. - Register the SP in SPDB using the output
sp_metadata_url— seedocs/stanford-saml-registration.md.
Both stacks provision a single Ubuntu VM that installs Docker, mounts a
dedicated persistent disk at /data (SQLite + SP keypair), obtains TLS, and
runs docker compose up.
| Route | Auth | Description |
|---|---|---|
GET / |
Stanford session | Hello page (user + workgroups + visit count) |
GET /api/health |
none | Liveness probe |
GET /api/me |
Stanford session | Authenticated identity + workgroups |
GET /api/hello |
workgroup member | Greeting; records a visit in SQLite |
/Shibboleth.sso/* |
— | SP handlers (ACS, metadata, logout, session) |
- SQLite is single-writer. This design is intentionally one VM with the DB
on a persistent disk — it does not scale horizontally. For multiple
instances, move to Postgres (RDS / Cloud SQL); the backend uses SQLAlchemy so
swapping
DATABASE_URLis most of the work. - Trust boundary: only the Shibboleth proxy may set the identity headers. Apache strips inbound copies; never expose the backend port publicly.
- State: Terraform state is local by default. For team use, configure a remote backend (S3 + DynamoDB, or a GCS bucket).
- MFA / attribute release are governed by Stanford; see the Attribute Release Policy.
- Stanford SAML service — https://uit.stanford.edu/service/saml
- Onboard a Service Provider — https://uit.stanford.edu/service/saml/onboard-service-provider
- Workgroup Manager — https://workgroup.stanford.edu · SPDB — https://spdb.stanford.edu
Reach out to DARC: gsb_darcresearch@stanford.edu