Files
backend/docs/CIE_AUTH_INTEGRATION.md
T

5.5 KiB

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:

"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:

{
  "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:
POST /api/v1/auth/cie/start
Content-Type: application/json
{
  "frontend_redirect_uri": "http://localhost:3000/auth/cie/callback"
}
  1. Redirect browser to authorization_url.
  2. On frontend callback page, read state from query string.
  3. Poll:
GET /api/v1/auth/cie/session/{state}
  1. Handle states:
  • pending: keep polling
  • authenticated: save auth.token
  • failed: show error and restart flow
  1. 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:
docker compose up -d
  1. Run Visione:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/visione-backend
  1. 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
  1. 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