Ga naar inhoud

Authenticatie (Frontend)

UI-Architectuur & Design

De authenticatie-interface is sober en functioneel en draait op zowel mobiele apps (Expo / React Native) als desktopbrowser (Expo web). Inloggen is een enkele stap; een verse her-authenticatie (step-up) verschijnt alleen wanneer een gevoelige actie of patientmodule dat vraagt.

  • Login: LoginScreen vraagt e-mail en wachtwoord. Op een gekoppeld mobiel toestel toont het scherm een biometrieknop, en op alle platforms knoppen om met ZORG-ID, Google of Apple in te loggen of te koppelen. Op desktop biedt het scherm daarnaast een QR-tab om cross-device in te loggen.
  • Step-up: gevoelige acties renderen achter StepUpGate. StepUpHost toont StepUpPanel (6-cijferige code) of StepUpQr (laat de telefoon de websessie verhogen). De elevatie geldt daarna een korte periode, zodat opeenvolgende acties niet telkens opnieuw bevestigd hoeven te worden.
  • Web-inlog via QR: op desktop start de QR-tab van LoginScreen een intent en wacht via WebSocket op approval. Op een vertrouwd mobiel toestel scant QrScannerScreen de code en bevestigt WebLoginApproveScreen de aanvraag.
  • Apparaatkoppeling: na een geslaagde login biedt BiometricSetupOffer aan om het toestel te koppelen, zodat latere logins en step-ups met FaceID, TouchID of vingerafdruk gaan (expo-local-authentication).
  • Meerdere apotheken: wie lid is van meerdere apotheken kiest na het inloggen via TenantChooserModal en wisselt later zonder opnieuw in te loggen.
  • Responsiviteit: dezelfde schermen draaien op telefoon, tablet en webbrowser. Op web zijn de tokens gekoppeld aan secure cookies; op native worden tokens in SecureStore bewaard.

State & Data Flow

Het hart van de authenticatie is de AuthProvider (src/features/auth/state/auth.tsx). Deze context beheert hydratie, tokens, gebruikerscontext en helpers voor permissies en modules. De step-up-state leeft apart in src/features/auth/state/stepUp.tsx.

  • Tokenopslag: op native via expo-secure-store (laag src/storage/storage.ts). Op web schrijft de backend access en refresh als secure cookies (zie common/auth_cookies.py); de frontend gebruikt de tokens daarnaast voor Authorization-headers waar nodig.
  • Login: LoginScreen roept loginPassword(email, password) aan. Bij een enkel lidmaatschap komen de tokens direct terug. Bij meerdere lidmaatschappen geeft de backend een login_state plus lidmaatschapslijst terug en kiest de gebruiker via TenantChooserModal, dat switchTenant aanroept.
  • Step-up: useStepUp() (src/features/auth/hooks/useStepUp.ts) opent de step-up wanneer een verzoek 403 met step-up-vereiste teruggeeft, en herhaalt de oorspronkelijke actie zodra de elevatie actief is. De verhoging gebeurt met een TOTP-code of met een biometrische bevestiging op een vertrouwd toestel, ook via de QR-route waarbij de telefoon een ingelogde websessie verhoogt.
  • Auto-refresh: apiRequest in @/api/client ververst proactief het access token via POST /api/auth/token/refresh/. Bij een definitieve 401 stuurt de app de gebruiker terug naar /login.
  • Permissie- en module-gating: useAuth().hasPermission(code) en useAuth().hasModule(key) lezen de payload van GET /api/auth/me/. Acties worden gegate via <PermissionGate require="...">.
  • OIDC: de login- en koppelknoppen voor ZORG-ID, Google en Apple starten de OIDC-route. Op web komt de browser terug op /oauth-return, dat de eenmalige code inwisselt voor een sessie of bij meerdere apotheken de TenantChooserModal toont. Een nog ongekoppelde identiteit gaat naar het koppelscherm /oauth-link, waar de gebruiker met e-mail en wachtwoord bevestigt. Op native verloopt de terugkomst in het proces zelf. De backend-afhandeling staat in de sociale-login-documentatie.
  • Apparaatkoppeling: na een geslaagde login registreert de app het toestel via POST /api/auth/devices/register/. De server geeft eenmalig een device_secret terug, dat biometrisch gegate in SecureStore wordt bewaard voor latere device-login en step-up.
sequenceDiagram
    participant UI as LoginScreen
    participant API as authApi
    participant BE as Backend
    participant Auth as AuthProvider
    participant Chooser as TenantChooserModal

    UI->>API: loginPassword(email, password)
    API->>BE: POST /api/auth/login/password/
    alt enkel lidmaatschap
        BE-->>Auth: { access, refresh }
        Auth->>BE: GET /api/auth/me/
        BE-->>Auth: profiel + tenant + permissions + enabled_modules
        Auth-->>UI: redirect naar /
    else meerdere lidmaatschappen
        BE-->>UI: { requires_tenant_choice, login_state, memberships }
        UI->>Chooser: toon apotheekkeuze
        Chooser->>BE: POST /api/auth/switch-tenant/ (tenant_id, login_state)
        BE-->>Auth: { access, refresh }
    end

Step-up bij een gevoelige actie:

sequenceDiagram
    participant UI as Beveiligde actie
    participant SU as useStepUp / StepUpHost
    participant BE as Backend

    UI->>BE: API-call (bijv. patientmodule)
    BE-->>UI: 403 step-up vereist
    UI->>SU: open StepUpPanel / StepUpQr
    SU->>BE: POST /api/auth/step-up/ (TOTP-code of device-signature)
    BE-->>SU: elevatie geopend (30 min)
    SU->>BE: herhaal oorspronkelijke API-call
    BE-->>UI: 200

Web-inlog via QR op desktop:

sequenceDiagram
    participant Web as LoginScreen (web, QR-tab)
    participant Phone as QrScannerScreen + WebLoginApproveScreen
    participant BE as Backend
    participant WS as WebSocket weblogin_<intent_id>

    Web->>BE: POST /api/auth/weblogin/start/
    BE-->>Web: { intent_id, challenge, expires_in }
    Web->>WS: subscribe weblogin_<intent_id>
    Phone->>BE: POST /api/auth/weblogin/confirm-device/ (challenge, signature)
    BE-->>WS: approved
    WS-->>Web: APPROVED
    Web->>BE: POST /api/auth/weblogin/fetch-code/
    BE-->>Web: { exchange_code }
    Web->>BE: POST /api/auth/weblogin/exchange/
    BE-->>Web: { access, refresh }

De Expo Router-routes onder frontend/app/ zijn dunne wrappers die op basis van AuthProvider-state redirecten.

  • /login: toont LoginScreen voor uitgelogde gebruikers; ingelogde gebruikers gaan naar /.
  • /weblogin: native WebLoginApproveScreen voor het bevestigen van een desktop-inlogpoging.
  • /oauth-return: web-afhandeling van de terugkomst uit een OIDC-login of -koppeling (ZORG-ID, Google, Apple); wisselt de eenmalige code in voor een sessie en stuurt een nog ongekoppelde identiteit door naar /oauth-link.
  • /oauth-link: koppelscherm voor een nog ongekoppelde OIDC-identiteit; de gebruiker bevestigt met e-mail en wachtwoord om de identiteit aan het account te binden.
  • /wachtwoord-reset/[token]: validatie en bevestiging van een resetlink (geen auth vereist).
  • /uitnodiging/[token]: onboarding van een nieuwe teamgebruiker (sms, wachtwoord, TOTP).
  • /uitnodiging-apotheek/[token]: accepteren van een uitnodiging voor een tweede apotheek door een bestaande gebruiker.
  • /totp-reset/[token]: voltooien van een door een beheerder geinitieerde 2FA-reset.
  • /support-tenant-picker: superuser-flow om een tenant te selecteren in support-modus.

Er is geen rootlevel auth-guard: elke route leest AuthProvider-state zelf en redirect naar /login als de sessie ontbreekt of verlopen is.

Bestandsstructuur & Verantwoordelijkheden

  • src/features/auth/state/auth.tsx: AuthProvider, hydratie, tokenopslag, useAuth, hasPermission, hasModule, support-mode en vertrouwd-apparaat-state.
  • src/features/auth/state/stepUp.tsx: state voor de step-up-elevatie en de wachtrij van uitgestelde acties.
  • src/features/auth/hooks/useStepUp.ts: hook die step-up opent bij een 403 en de oorspronkelijke actie herhaalt.
  • src/features/auth/api/authApi.ts: typed wrappers rond alle /api/auth/...-endpoints (login, switch-tenant, step-up, refresh, me, devices, sessions, weblogin, password reset, OIDC, support).
  • src/features/auth/lib/webloginQr.ts: opbouwen en lezen van de QR-payload (intent_id, challenge).
  • src/features/auth/screens/LoginScreen.tsx: login met wachtwoord, biometrie, OIDC-knoppen en de QR-tab voor desktop.
  • src/features/auth/screens/OauthLinkScreen.tsx: koppelscherm dat een nog ongekoppelde OIDC-identiteit met e-mail en wachtwoord aan een account bindt.
  • src/features/auth/hooks/useLoginCompletion.ts: gedeelde afronding van een geslaagde wachtwoord- of OIDC-login (tokens dan wel de apotheekkeuze).
  • src/features/auth/screens/QrScannerScreen.tsx: native QR-scanner voor het goedkeuren van een desktop-inlog of step-up.
  • src/features/auth/screens/WebLoginApproveScreen.tsx: native scherm om een desktop-inlogpoging goed te keuren of te weigeren.
  • src/features/auth/screens/PasswordResetScreen.tsx: een nieuw wachtwoord instellen op basis van een geldige resettoken.
  • src/features/auth/screens/SecondTenantInviteScreen.tsx: accepteren of weigeren van een uitnodiging voor een tweede apotheek.
  • src/features/auth/screens/SupportTenantPickerScreen.tsx: tenantkeuze voor superusers in support-modus.
  • src/features/auth/components/StepUpGate.tsx, StepUpHost.tsx, StepUpPanel.tsx, StepUpQr.tsx: de step-up-UI (gate, host, code-paneel en QR-verhoging).
  • src/features/auth/components/TenantChooserModal.tsx: apotheekkeuze bij meerdere lidmaatschappen.
  • src/features/auth/components/BiometricSetupOffer.tsx: aanbod om na login het toestel te koppelen voor biometrie.
  • src/features/auth/components/AuthCard.tsx, AuthPanel.tsx, AuthAccordion.tsx, AuthSuccessCard.tsx: gedeelde lay-outprimitieven voor authschermen.
  • src/features/auth/components/ForgotPasswordModal.tsx: aanvragen van een wachtwoord-resetlink.
  • src/features/auth/components/GeneralTermsModal.tsx: weergave en acceptatie van de algemene voorwaarden.
  • src/features/auth/components/SupportModeBanner.tsx: baken bovenin de UI wanneer een superuser via support-modus in een tenant werkt.
  • src/storage/storage.ts: SecureStore-abstractie inclusief biometrie-gating voor gevoelige sleutels zoals device-secrets.

Belangrijke bestanden

  • src/features/auth/state/auth.tsx
  • src/features/auth/state/stepUp.tsx
  • src/features/auth/hooks/useStepUp.ts
  • src/features/auth/api/authApi.ts
  • src/features/auth/screens/LoginScreen.tsx
  • src/features/auth/screens/WebLoginApproveScreen.tsx
  • src/features/auth/components/StepUpHost.tsx
  • src/features/auth/components/TenantChooserModal.tsx