feat: Integrate CIE authentication flow

This commit is contained in:
gabdevele
2026-06-17 12:53:16 +02:00 Unverified
parent bb7ed18539
commit 6cbdf6462d
20 changed files with 946 additions and 15 deletions
+182
View File
@@ -0,0 +1,182 @@
# Visione CIE Authentication Integration
## Goal
Keep Visione backend simple.
Visione does not implement OIDC Federation or CIE protocol details directly.
`spid-cie-oidc-django` handles CIE login. Visione only manages:
- local login `state`
- user provisioning
- external identity linking
- local bearer token issuance
## Components
- Visione backend: `POST /api/v1/auth/cie/start`, `POST /api/v1/auth/cie/exchange`,
`GET /api/v1/auth/cie/session/{state}`
- Django RP service: `examples/relying_party`
- Frontend: browser redirect + polling
## End-to-End Flow
1. Frontend calls `POST /api/v1/auth/cie/start` with `frontend_redirect_uri`.
2. Visione stores row in `cie_auth_sessions` and returns `authorization_url`.
3. Frontend redirects browser to returned Django URL.
4. Django RP performs CIE login with provider.
5. On success Django extracts user attributes and sends signed JSON payload to
Visione `POST /api/v1/auth/cie/exchange`.
6. Visione verifies HMAC signature, validates `state`, finds or creates user,
stores `user_external_identities`, issues `api_tokens`, marks session authenticated.
7. Visione responds to Django with HTTP redirect to
`frontend_redirect_uri?state=<state>`.
8. Frontend reads `state` from query string and polls
`GET /api/v1/auth/cie/session/{state}` until authenticated.
9. Frontend stores returned Visione bearer token and calls `GET /api/v1/me`.
## Why This Architecture
- No OIDC/Federation crypto in C++ backend.
- No CIE protocol tables spread across Visione domain code.
- Django demo repo stays reusable and isolated.
- Visione keeps final auth authority for application tokens and user model.
## Visione Backend Config
`config.json`:
```json
"cie_auth": {
"service_base_url": "http://127.0.0.1:8001",
"start_path": "/visione/start",
"shared_secret": "change-me-for-local-dev",
"state_ttl_minutes": 10,
"default_frontend_redirect_uri": "http://localhost:3000/auth/cie/callback"
}
```
Meaning:
- `service_base_url`: Django RP base URL
- `start_path`: Django entrypoint used by frontend redirect
- `shared_secret`: HMAC secret shared only between Visione and Django
- `state_ttl_minutes`: local login state lifetime
- `default_frontend_redirect_uri`: fallback if frontend omits value
## Visione DB Additions
- `cie_auth_sessions`
stores local state, frontend redirect, final user id, final bearer token
- `user_external_identities`
stores `provider`, `issuer`, `subject`, `fiscal_code`, raw claims
User matching strategy:
1. exact external identity on `provider + issuer + subject`
2. fallback reunification on `fiscal_code`
3. otherwise create new `citizen`
## Signed Exchange Contract
Django sends `POST /api/v1/auth/cie/exchange` with JSON:
```json
{
"state": "visione-login-state",
"issuer": "http://127.0.0.1:8002/oidc/op",
"subject": "provider-subject",
"fiscal_code": "RSSMRA80A01H501U",
"first_name": "Mario",
"last_name": "Rossi",
"email": "[email protected]"
}
```
Headers:
- `Content-Type: application/json`
- `X-Visione-Signature: <UPPERCASE_HEX_HMAC_SHA256>`
Signature algorithm:
- payload bytes exactly as sent
- key = `cie_auth.shared_secret`
- HMAC-SHA256
- uppercase hex output
## Django Example Customization
Modified files in `spid-cie-oidc-django/examples/relying_party`:
- `relying_party/settingslocal.py.example`
- `relying_party/urls.py`
- `relying_party/visione_views.py`
Added endpoints:
- `GET /visione/start`
stores Visione `state` in Django session, then redirects to `/oidc/rp/authorization`
- `GET /visione/complete`
reads `oidc_rp_user_attrs`, signs payload, calls Visione exchange endpoint,
then redirects browser to frontend callback URL returned by Visione
Important:
- default OIDC callback remains managed by `spid_cie_oidc.relying_party`
- `LOGIN_REDIRECT_URL` now points to `/visione/complete`
- no need to modify RP metadata dump callback URI for this integration
## Frontend Responsibilities
1. Call:
```http
POST /api/v1/auth/cie/start
Content-Type: application/json
```
```json
{
"frontend_redirect_uri": "http://localhost:3000/auth/cie/callback"
}
```
2. Redirect browser to `authorization_url`.
3. On frontend callback page, read `state` from query string.
4. Poll:
```http
GET /api/v1/auth/cie/session/{state}
```
5. Handle states:
- `pending`: keep polling
- `authenticated`: save `auth.token`
- `failed`: show error and restart flow
6. After token, call `GET /api/v1/me`.
Frontend does not need:
- CIE secrets
- Django shared secret
- OIDC token parsing
## Local Dev Setup
1. Start Visione dependencies:
```bash
docker compose up -d
```
2. Run Visione:
```bash
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/visione-backend
```
3. In separate repo checkout for `spid-cie-oidc-django`, configure:
- federation authority example
- provider example
- relying party example with `VISIONE_BACKEND_EXCHANGE_URL`
- same `VISIONE_SHARED_SECRET` as Visione `cie_auth.shared_secret`
4. Start Django RP on `http://127.0.0.1:8001`.
## Security Notes
- Shared secret must never reach frontend.
- `cie_auth.shared_secret` must change outside local dev.
- Keep `state_ttl_minutes` short.
- Visione bearer token stays out of query string; only `state` goes to frontend.
- Django service is trusted bridge. Protect it as internal auth component.
## Future Improvements
- Mark failed sessions explicitly when Django login fails
- Rotate HMAC secret through environment variables
- Add replay protection with timestamp/nonce in signed payload
- Add integration tests between Django RP and Visione