feat(ociserve): meld opnieuw aan als de organisatie een sterkere aanmelding eist #2385

Merged
brenno merged 3 commits from claude/ociserve-step-up-challenge into main 2026-10-11 17:45:40 +00:00
Owner

Aanleiding

OciServe kan een organisatie een recente of sterkere aanmelding laten eisen (strikt toegangsbeleid, OciServe #652). De server antwoordt dan 401 met een RFC 9470-uitdaging (insufficient_user_authentication, acr_values, max_age). OciDeck behandelde elke 401 als "uitgelogd". Dat helpt niet: een token vernieuwen levert dezelfde auth_time en acr, en opnieuw inloggen zonder max_age laat Keycloak de bestaande browsersessie hergebruiken. Het resultaat was een inloglus. Dit is de clientkant. Het is achterwaarts compatibel: zonder uitdaging verandert er niets.

Wat dit doet

  • Gateway: _send herkent de uitdaging (parseOciServeStepUpChallenge) en herhaalt het verzoek één keer met het verse token, met dezelfde methode, URL, body en Idempotency-Key. Alle 26 aanroepplekken in de provider blijven ongewijzigd. Een tweede uitdaging is een fout, geen lus.
  • Authenticator: login(stepUp:) zet acr_values en max_age in het autorisatieverzoek.
  • Provider: elke gateway krijgt een handler die interactief opnieuw aanmeldt, de identiteit controleert en het nieuwe token pas dan in gebruik neemt.

Veiligheid, elk met een test die rood wordt als het ontbreekt

  • Het nieuwe token vervangt het oude pas als /me hetzelfde account en een actief lidmaatschap toont. Een ander account in de browser (gedeelde computer) wordt geweigerd en de eigen sessie blijft staan.
  • Gelijktijdige verzoeken delen één aanmelding; een verzoek dat voor een afgeronde aanmelding werd opgebouwd gebruikt het resultaat in plaats van een tweede browser te openen.
  • Werk dat de gebruiker niet startte opent nooit een browser: voortgang op de achtergrond, plaatjes, een lessessie sluiten, uitloggen.
  • Een aanmelding die afrondt nadat is uitgelogd verandert niets (geen token, geen account), met en zonder "ingelogd blijven".
  • De waarden komen van de server en gaan naar de identiteitsprovider, dus ze worden begrensd: 2 KiB, hoogstens acht acr van afdrukbaar ASCII, een leeftijd tot 366 dagen, en error moet een echte parameter zijn.

Tests en bewijs

Drie niveaus (parser en gateway, authenticator, provider). Elke regel is rood bewezen door hem gericht te breken: ruim twintig mutaties. Twee zwakke plekken die dat aan het licht bracht zijn eerst verholpen: de test voor "uitgelogd tijdens het wachten" ving de verwijdering van de generatiecontroles niet (een andere bewaking dekte het toevallig; nu getest met en zonder opslag), en de mutaties van het sluiten van lessessies waren zelf kapot. Ook één harnasfout van mijn kant: een mutatie liet de type-controle vallen en gaf een compileerfout in plaats van een rode test; die is herhaald met een mutatie die wel compileert.

Poorten

  • make check volledig groen (exit 0, uit het logbestand gelezen) op 099c4200b: statische poorten, volledige suite, coverage, per-bestandsvloer, pakketten en golden.
  • make check-secrets en make sast groen op diezelfde commit (0 findings). main is daarna niet verschoven.
  • Er vuurt geen poort per PR op de forge; dit zijn de uitslagen waarop gemerged wordt.

Bewaker-weging

Raakt een sleutel (het vernieuwtoken wordt bijgewerkt) en een belofte in de documentatie, dus de bewaker heeft gekeken.

  • Botsing: gemak tegen veiligheid. Een browser die vanzelf opent bij een uitdaging is gemak (geen extra klik). Veiligheid gaat voor: een ander account wordt geweigerd, en achtergrondwerk en uitloggen openen nooit een browser. Daar geef ik het gemak op.
  • Wie moet je vertrouwen: geen nieuwe partij. Het is dezelfde identiteitsprovider als bij de eerste aanmelding; de uitdaging komt van de eigen OciServe-server over de vastgepinde HTTPS-verbinding.
  • Niets in het formaat of de opslag van presentaties verandert; geen nieuwe afhankelijkheid.
  • Ik zou dit herzien als een organisatie een stap-omhoog midden in een formeel examen niet wil laten onderbreken; dan hoort daar een bevestiging vóór de browser.

Beperkingen

  • Pas merkbaar zodra OciServe #652 is uitgerold. Een acr-eis werkt pas als Keycloak acr uitgeeft (nu niet; zie ADR 0017). Een strikte max_auth_age werkt direct.
  • Geen nieuwe interfacetekst: een mislukte stap-omhoog (gesloten browser, ander account) valt onder de algemene foutmelding van het scherm. Een eigen melding vraagt 31 vertalingen en kan als vervolg.
  • Keiko (de beheerapp) is een aparte PR.

🤖 Generated with Claude Code

## Aanleiding OciServe kan een organisatie een recente of sterkere aanmelding laten eisen (strikt toegangsbeleid, [OciServe #652](https://pawprint.vigilis.online/brenno/OciServe/pulls/652)). De server antwoordt dan `401` met een RFC 9470-uitdaging (`insufficient_user_authentication`, `acr_values`, `max_age`). OciDeck behandelde elke `401` als "uitgelogd". Dat helpt niet: een token vernieuwen levert dezelfde `auth_time` en `acr`, en opnieuw inloggen zonder `max_age` laat Keycloak de bestaande browsersessie hergebruiken. Het resultaat was een inloglus. Dit is de clientkant. Het is achterwaarts compatibel: zonder uitdaging verandert er niets. ## Wat dit doet - **Gateway**: `_send` herkent de uitdaging (`parseOciServeStepUpChallenge`) en herhaalt het verzoek één keer met het verse token, met dezelfde methode, URL, body en `Idempotency-Key`. Alle 26 aanroepplekken in de provider blijven ongewijzigd. Een tweede uitdaging is een fout, geen lus. - **Authenticator**: `login(stepUp:)` zet `acr_values` en `max_age` in het autorisatieverzoek. - **Provider**: elke gateway krijgt een handler die interactief opnieuw aanmeldt, de identiteit controleert en het nieuwe token pas dan in gebruik neemt. ## Veiligheid, elk met een test die rood wordt als het ontbreekt - Het nieuwe token vervangt het oude pas als `/me` hetzelfde account en een actief lidmaatschap toont. Een ander account in de browser (gedeelde computer) wordt geweigerd en de eigen sessie blijft staan. - Gelijktijdige verzoeken delen één aanmelding; een verzoek dat voor een afgeronde aanmelding werd opgebouwd gebruikt het resultaat in plaats van een tweede browser te openen. - Werk dat de gebruiker niet startte opent **nooit** een browser: voortgang op de achtergrond, plaatjes, een lessessie sluiten, uitloggen. - Een aanmelding die afrondt nadat is uitgelogd verandert niets (geen token, geen account), met en zonder "ingelogd blijven". - De waarden komen van de server en gaan naar de identiteitsprovider, dus ze worden begrensd: 2 KiB, hoogstens acht `acr` van afdrukbaar ASCII, een leeftijd tot 366 dagen, en `error` moet een echte parameter zijn. ## Tests en bewijs Drie niveaus (parser en gateway, authenticator, provider). Elke regel is **rood bewezen door hem gericht te breken**: ruim twintig mutaties. Twee zwakke plekken die dat aan het licht bracht zijn eerst verholpen: de test voor "uitgelogd tijdens het wachten" ving de verwijdering van de generatiecontroles niet (een andere bewaking dekte het toevallig; nu getest met en zonder opslag), en de mutaties van het sluiten van lessessies waren zelf kapot. Ook één harnasfout van mijn kant: een mutatie liet de type-controle vallen en gaf een compileerfout in plaats van een rode test; die is herhaald met een mutatie die wel compileert. ## Poorten - `make check` volledig groen (exit 0, uit het logbestand gelezen) op `099c4200b`: statische poorten, volledige suite, coverage, per-bestandsvloer, pakketten en golden. - `make check-secrets` en `make sast` groen op diezelfde commit (0 findings). `main` is daarna niet verschoven. - Er vuurt geen poort per PR op de forge; dit zijn de uitslagen waarop gemerged wordt. ## Bewaker-weging Raakt een sleutel (het vernieuwtoken wordt bijgewerkt) en een belofte in de documentatie, dus de bewaker heeft gekeken. - **Botsing: gemak tegen veiligheid.** Een browser die vanzelf opent bij een uitdaging is gemak (geen extra klik). Veiligheid gaat voor: een ander account wordt geweigerd, en achtergrondwerk en uitloggen openen nooit een browser. Daar geef ik het gemak op. - **Wie moet je vertrouwen:** geen nieuwe partij. Het is dezelfde identiteitsprovider als bij de eerste aanmelding; de uitdaging komt van de eigen OciServe-server over de vastgepinde HTTPS-verbinding. - Niets in het formaat of de opslag van presentaties verandert; geen nieuwe afhankelijkheid. - **Ik zou dit herzien als** een organisatie een stap-omhoog midden in een formeel examen niet wil laten onderbreken; dan hoort daar een bevestiging vóór de browser. ## Beperkingen - Pas merkbaar zodra OciServe #652 is uitgerold. Een `acr`-eis werkt pas als Keycloak `acr` uitgeeft (nu niet; zie ADR 0017). Een strikte `max_auth_age` werkt direct. - Geen nieuwe interfacetekst: een mislukte stap-omhoog (gesloten browser, ander account) valt onder de algemene foutmelding van het scherm. Een eigen melding vraagt 31 vertalingen en kan als vervolg. - Keiko (de beheerapp) is een aparte PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Een organisatie kan een recente of sterkere aanmelding eisen; OciServe
antwoordt dan `401` met `WWW-Authenticate: Bearer
error="insufficient_user_authentication"`, `acr_values` en `max_age`. Voor
OciDeck gold zo'n `401` als "niet ingelogd", en dat helpt niet: een token
vernieuwen levert dezelfde `auth_time` en `acr`.

`_send` is de ene plek waar elk ingelogd verzoek doorheen gaat. Daar wordt de
uitdaging nu gelezen (`parseOciServeStepUpChallenge`) en, als de gateway een
`stepUpHandler` heeft, het verzoek één keer herhaald met het token dat de
handler levert: dezelfde methode, URL, body en `Idempotency-Key`, zodat een
herhaling nooit een tweede boeking of rapport wordt. Een tweede uitdaging na
de herhaling is een fout en geen lus. Zonder handler gooit de gateway
`OciServeStepUpRequired`.

De waarden komen van de server en gaan straks in een autorisatieverzoek bij de
identiteitsprovider, dus ze worden begrensd: 2 KiB, hoogstens acht `acr`
van afdrukbaar ASCII, een leeftijd tot 366 dagen. `error` moet een echte
parameter zijn, geen tekst in een waarde.

Elke regel is rood bewezen door hem gericht te breken.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
De provider geeft elke gateway een handler die bij een uitdaging interactief
opnieuw aanmeldt, met de `acr_values` en `max_age` uit de uitdaging. Zonder
`max_age` hergebruikt Keycloak de bestaande browsersessie met de oude
`auth_time`, en dan blijft de uitdaging terugkomen.

Wat dat veilig maakt, elk met een test die rood wordt als het ontbreekt:
- het nieuwe token vervangt het oude pas als `/me` hetzelfde account en een
  actief lidmaatschap toont: een ander account in de browser (gedeelde
  computer) wordt geweigerd en de eigen sessie blijft staan;
- gelijktijdige verzoeken delen één aanmelding, en een verzoek dat voor die
  aanmelding werd opgebouwd gebruikt het resultaat in plaats van een tweede
  browser te openen;
- werk dat de gebruiker niet startte opent nooit een browser: voortgang op de
  achtergrond, plaatjes, een lessessie sluiten en uitloggen;
- een aanmelding die afrondt nadat is uitgelogd verandert niets: geen token,
  geen account;
- het vernieuwtoken wordt alleen bijgewerkt als de gebruiker ingelogd wil
  blijven, en een mislukte aanmelding vergiftigt de volgende poging niet.

Geen nieuwe interfacetekst: een mislukte stap-omhoog valt onder de algemene
foutmelding van het scherm.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Handleiding (EN en NL), beveiligingsontwerp, API-documentatie en changelog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brenno merged commit f7231eaef4 into main 2026-10-11 17:45:40 +00:00
Sign in to join this conversation.
No description provided.