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:
LoginScreenvraagt 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.StepUpHosttoontStepUpPanel(6-cijferige code) ofStepUpQr(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
LoginScreeneen intent en wacht via WebSocket op approval. Op een vertrouwd mobiel toestel scantQrScannerScreende code en bevestigtWebLoginApproveScreende aanvraag. - Apparaatkoppeling: na een geslaagde login biedt
BiometricSetupOfferaan 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
TenantChooserModalen 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
SecureStorebewaard.
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(laagsrc/storage/storage.ts). Op web schrijft de backendaccessenrefreshals secure cookies (ziecommon/auth_cookies.py); de frontend gebruikt de tokens daarnaast voorAuthorization-headers waar nodig. - Login:
LoginScreenroeptloginPassword(email, password)aan. Bij een enkel lidmaatschap komen de tokens direct terug. Bij meerdere lidmaatschappen geeft de backend eenlogin_stateplus lidmaatschapslijst terug en kiest de gebruiker viaTenantChooserModal, datswitchTenantaanroept. - 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:
apiRequestin@/api/clientververst proactief het access token viaPOST /api/auth/token/refresh/. Bij een definitieve 401 stuurt de app de gebruiker terug naar/login. - Permissie- en module-gating:
useAuth().hasPermission(code)enuseAuth().hasModule(key)lezen de payload vanGET /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 deTenantChooserModaltoont. 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 eendevice_secretterug, dat biometrisch gegate inSecureStorewordt 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 }
Navigatie & Routing
De Expo Router-routes onder frontend/app/ zijn dunne wrappers die op basis van AuthProvider-state redirecten.
/login: toontLoginScreenvoor uitgelogde gebruikers; ingelogde gebruikers gaan naar/./weblogin: nativeWebLoginApproveScreenvoor 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.tsxsrc/features/auth/state/stepUp.tsxsrc/features/auth/hooks/useStepUp.tssrc/features/auth/api/authApi.tssrc/features/auth/screens/LoginScreen.tsxsrc/features/auth/screens/WebLoginApproveScreen.tsxsrc/features/auth/components/StepUpHost.tsxsrc/features/auth/components/TenantChooserModal.tsx