SSO infrastructure for Rézoléo using Zitadel.
This project manages centralized authentication for Rézoléo services. It consists of:
- Zitadel - Open-source identity provider (IdP) handling OAuth2/OIDC flows
- Registration Service - Custom FastAPI app for student onboarding
- Action Target - Custom FastAPI webhook service for Zitadel Actions V2 (e.g. for validation)
graph LR
SR([Student Registration]):::input
subgraph Backend["rezoleo-auth"]
RS[registration-service]
Z[Zitadel]
AT[action-target]
RS --> Z
Z --> AT
AT --> Z
end
AT -.-> DC[Discord]
Z -.-> DB[(pg.rezoleo.fr)]
SR --> RS
Web interface for students to create accounts with their school email.
Key features:
- Validates
prenom.nom@{centrale|enscl|iteem|ig2i}.centralelille.fremails - Auto-generates unique usernames (e.g.,
jean-dupont,jean-dupont-1) - Creates users in Zitadel via Rézoléo machine account
- Serves static frontend (HTML/JS form)
Example flow:
# POST /register
{
"email": "jean.paul@centrale.centralelille.fr",
"first_name": "Jean-Paul",
"last_name": "Dupont"
}
# → Creates user with:
# - username: jean-paul-dupont
# - school metadata: centraleZitadel then automatically sends verification email (could be customized).
Key files:
backend/main.py- FastAPI endpointsbackend/zitadel_client.py- Zitadel API wrapperbackend/utils.py- Email parsing, username sanitizationfrontend/- Registration HTML form
FastAPI webhook receiver for Zitadel Actions V2 (event triggers).
Implemented actions:
/on-user-created- Sends notification when new user registers/on-user-updated- Blocks username changes by non-admins, validates username format/on-userinfo- Enriches OIDC userinfo with project-specific roles
Example: Role injection
# Zitadel user grants:
# - Project "REZOLEO": roles ["admin", "rezoleo"]
# → OIDC claims:
{
"roles-REZOLEO": ["admin", "member"]
}- Docker & Docker Compose
- PostgreSQL (external, configured in
.env) - Python 3.13+ (for local development)
Create .env from .env.example:
POSTGRES_ADMIN_PASSWORD=...
ADMIN_ACCOUNT_PASSWORD=...
ZITADEL_REZOLEO_PAT=...
DISCORD_WEBHOOK_URL=...Generate master.key for Zitadel:
openssl rand -base64 32 > master.keydocker-compose up -dServices:
- Zitadel: http://localhost:8080
- Registration: http://localhost:8000
- Action Target: http://localhost:8001
Registration service:
cd registration-service
uv run uvicorn backend.main:app --reload --port 8000Action target:
cd action-target
uv run uvicorn main:app --reload --port 8001The registration service uses Zitadel's v2 API:
POST /v2/users- Search users (by email/username)POST /v2/users/new- Create human user with metadata
Auth: Personal Access Token (PAT) for Rézoléo machine user.
Configure in Zitadel Default Settings:
| Condition | Type | Target |
|---|---|---|
method: /zitadel.user.v2.UserService/CreateUser |
Response | http://action-target:8001/on-user-created |
method: /zitadel.user.v2.UserService/UpdateHumanUser |
Request | http://action-target:8001/on-user-updated |
funtion: preuserinfo |
Function | http://action-target:8001/on-userinfo |
Be careful when you set up targets to take responses into account by selecting the right Type in the target configuration.
- Why Zitadel? Open-source, self-hosted, standards-compliant (OAuth2/OIDC), extensible via Actions V2
- Email-based registration: School emails enforce .centralelille.fr domain, auto-extract school affiliation
- Username format: Unix-like (
[a-z][a-z0-9_-]{0,30}), collision-resistant with numeric suffixes - Role claims: Custom
roles-{project}format allows multi-tenant authorization
- Registration endpoint is public
- Username changes blocked for non-admin users
- David Marembert - GitHub