Each line is an obligation of the app and a test to run. The IdP enforces its side (scopes, PKCE, exact redirects, token audience, lifetimes) regardless of the app’s code; the lines below are the app’s part. Proofs that need the IdP administration (a test account, blocking it, a restart): request them at suporte@institutourupema.com.br.
| Check | How to prove it |
|---|
A production descriptor separate from the development one: exact https redirects, postLogoutRedirectUris filled in, no localhost | node reconcile.ts --check passes in the pull request (Registering an app) |
| The client secret only in the server’s environment or a vault | git grep finds no secret; logs never print it; the browser’s network tab never shows it |
| Check | How to prove it |
|---|
| Endpoints come from discovery; only the issuer is configured | no IdP URL in the code besides IDP_ISSUER |
Authorization Code + PKCE S256, state, nonce, scope openid profile email | the authorization request (browser devtools) carries code_challenge_method=S256, state, nonce and exactly that scope |
The ID token is fully validated: signature, iss, aud, exp, nonce | a callback with a tampered state is refused; the library’s configuration verifies the signature (openid-client: enableNonRepudiationChecks) |
| No call to the Keycloak admin API, its database or any engine-specific path | only discovery, authorization, token, userinfo, JWKS and end-session URLs are used |
| Check | How to prove it |
|---|
People are keyed by a unique (iss, sub); email is an attribute | changing the email in the IdP updates the same record at the next login |
| A new account has zero permissions in the app | sign in with a freshly created test account (from the administration): it sees and does only what a user with no grant may |
An invitation by email binds only when email_verified is true, once | an invited email that is not verified gets nothing; after binding, changing the email moves no access |
| Check | How to prove it |
|---|
Tokens stored on the server, in a store that survives restarts; the cookie is HttpOnly, Secure, SameSite=Lax | restart the app: people stay signed in; the cookie holds only an id |
| Refresh on expiry; a refused or failed refresh destroys the local session and answers 401 | have the administration block the test account (disable + end sessions): within 5 minutes the app answers 401, and the session does not come back when the account is re-enabled |
| The local session never outlives the central one (10 h) | the cookie’s maxAge is at most 36000 s; once the central session ends, the next request after the refresh answers 401 |
Logout ends the local session and redirects to end_session_endpoint with id_token_hint | after logout, opening any app of the Institute asks for the password |
| The app survives an IdP restart | during a restart agreed with the administration, everyone is sent to the login within 5 minutes, without an error page |
| Check | How to prove it |
|---|
The API validates the access token: signature, iss, exp, aud containing the app’s client_id, typ = Bearer | requests with no token, an expired token, another client’s token or an ID token get 401 (API: validate the token) |