# Visione CIE OIDC RP Technical Plan ## Goal Implement Visione backend as SPID/CIE OIDC Federation Relying Party compatible with local demo provider contained in `spid-cie-oidc-django/examples/provider`. Scope target for first real implementation: - compatibility with demo provider only - CIE profile only - Visione backend remains source of truth for application auth - frontend drives browser redirect flow ## Local Reference Mapping - Demo OP/federation behavior source: - `spid-cie-oidc-django/examples/provider` - `spid-cie-oidc-django/spid_cie_oidc/provider` - `spid-cie-oidc-django/spid_cie_oidc/entity` - Demo RP behavior to emulate in Drogon: - `spid-cie-oidc-django/spid_cie_oidc/relying_party/views/rp_begin.py` - `spid-cie-oidc-django/spid_cie_oidc/relying_party/views/rp_callback.py` ## Current Status - `common/OidcJwt.*` now owns JWT/JWS/JWK work through `jwt-cpp` - `common/OidcCrypto.*` isolates OpenSSL-only JWE compact parsing and decrypt - `POST /api/v1/auth/cie/start` signs the request object with RP signing key - callback exchanges code with `private_key_jwt`, verifies token JWS and `at_hash` - `userinfo` GET supports plain JSON and demo-compatible nested JWE -> JWS - merged OIDC claims persist through `user_external_identities` - frontend polling contract is active through `GET /api/v1/auth/cie/session/{state}` ## Foundation Added In This Step - config placeholders in `config.json` - DB schema for OIDC auth sessions, tokens, federation cache, external identities - Drogon controller skeleton in `controllers/OidcController.*` - service skeleton in `services/OidcRpService.*` - RP config helper in `common/OidcRpConfig.*` - RP public endpoints reserved: - `POST /api/v1/auth/cie/start` - `GET /api/v1/auth/cie/callback` - `GET /api/v1/auth/cie/session/{state}` - `GET /oidc/rp/.well-known/openid-federation` - `GET /oidc/rp/resolve` - `GET /oidc/rp/jwks.json` ## Residual Backlog ### Phase 1: Dependencies and crypto plumbing - Done for demo scope: - `jwt-cpp` handles JWT parsing, signing and signature verification - custom OpenSSL code remains only for JWE decryption in `OidcCrypto` - focused unit tests cover JWT helpers and both supported JWE CBC/HMAC modes ### Phase 2: RP metadata and JWKS - Done for demo flow. - Residual gap: - verify metadata fields against non-demo providers before reuse ### Phase 3: Federation trust resolution - Current cut: - fetches trust-anchor `resolve` - extracts OP metadata/JWKS/endpoints - Not done: - full trust-chain signature validation - federation cache population/reuse - multi-provider or multi-anchor logic ### Phase 4: Authorization start - Done. - Demo scope now sent explicitly as `openid cie_demo`. ### Phase 5: Callback and token exchange - Done for demo provider. - Residual gap: - explicit `exp` / `iat` skew checks still thin - no refresh-token lifecycle use yet ### Phase 6: UserInfo and local account link - Done for demo provider, including JWE decrypt + JWS verify + merged claims. ### Phase 7: Frontend completion contract - Done. - Session payload now exposes: - public `status` - internal `phase` - `auth` payload only on success ### Phase 8: Tests and docs - Unit tests added for: - JWT parse/sign/verify - `kid` lookup - `at_hash` helper - JWE decrypt for `A256CBC-HS512` - JWE decrypt for `A128CBC-HS256` - invalid JWE tag / unsupported enc - merged claim precedence and fiscal code normalization - Still missing: - automated integration tests against live demo Django OP ## Data Model Notes ### `oidc_authentications` Stores single login transaction state and provider snapshot. ### `oidc_authentication_tokens` Stores authorization code and received tokens. ### `oidc_federation_cache` Stores fetched federation docs with expiry. ### `user_external_identities` Decouples local user from external subject identifiers. ## Current Decisions - Keep OTP auth alive in parallel - Use local `api_tokens` after OIDC success - Use `fiscal_code` as strongest user reunification key - Do not expose Visione token in query string - Prefer external validated libs over handwritten JWT/JWK code ## Ready Criteria For Real Implementation - foundation schema merged - config keys finalized - dependency choice finalized - endpoint shapes stable enough for frontend When these are acceptable, next step is real implementation of Phase 1-4. ## Demo CIE Runbook ### Prerequisites - MySQL and Mailpit running locally through this repo `docker compose` - RP keys available in `secrets/oidc/demo-rp-jwks.json` - backend `config.json` aligned with: - trust anchor `http://127.0.0.1:8000` - provider issuer `http://127.0.0.1:8002/oidc/op` - callback `http://127.0.0.1:5555/api/v1/auth/cie/callback` ### Bootstrap local dependencies ```bash docker compose up -d cmake -S . -B build_local -G Ninja -DCMAKE_BUILD_TYPE=Debug cmake --build build_local ``` ### Bootstrap demo federation authority ```bash cd spid-cie-oidc-django/examples/federation_authority cp federation_authority/settingslocal.py.example federation_authority/settingslocal.py python3 -m pip install --upgrade pip python3 -m pip install -e ../.. python3 -m pip install "design-django-theme==v1.4.8" ./manage.py migrate ./manage.py loaddata dumps/example.json ./manage.py runserver 0.0.0.0:8000 ``` ### Bootstrap demo provider ```bash cd spid-cie-oidc-django/examples/provider cp provider/settingslocal.py.example provider/settingslocal.py ./manage.py migrate ./manage.py loaddata dumps/example.json ./manage.py runserver 0.0.0.0:8002 ``` ### Start Visione backend ```bash ./build_local/visione-backend ``` ### Start login ```bash curl -sS -X POST http://127.0.0.1:5555/api/v1/auth/cie/start \ -H 'Content-Type: application/json' \ -d '{"provider":"cie_demo","frontend_redirect_uri":"http://localhost:3000/auth/cie/callback"}' ``` Expected result: - `authorization_url` present - `state` present - one row in `oidc_authentications` with phase `AUTHORIZATION_REDIRECT_READY` ### Complete browser flow 1. Open returned `authorization_url`. 2. Authenticate with the demo CIE user configured by the Django fixtures. 3. Accept consent if requested. 4. Provider redirects to backend callback. 5. Backend redirects frontend to `frontend_redirect_uri?state=...`. ### Poll login result ```bash curl -sS http://127.0.0.1:5555/api/v1/auth/cie/session/ ``` Expected success payload: - `status = "authenticated"` - `phase = "AUTHENTICATED"` - `auth.token` present ### Minimal DB verification ```sql SELECT state,status,authenticated_user_id FROM oidc_authentications ORDER BY id DESC LIMIT 3; SELECT authentication_id,token_type,scope FROM oidc_authentication_tokens ORDER BY id DESC LIMIT 3; SELECT user_id,provider,issuer,subject,fiscal_code FROM user_external_identities ORDER BY id DESC LIMIT 3; SELECT user_id,expires_at FROM api_tokens ORDER BY id DESC LIMIT 3; ``` ### Common failures - `provider_metadata_missing`: federation authority not started or wrong trust anchor URL - `token_exchange_failed`: provider callback reached but token endpoint unavailable or RP assertion invalid - `invalid_userinfo_response`: JWE decryption key mismatch or unexpected provider response format - `at_hash_mismatch`: provider tokens not aligned with received `access_token`