Single sign-on for single-page applications
This page explains how to set up single sign-on (SSO) for single-page applications (SPAs) that use nevisAuth as an OpenID Provider. After the user logs in to one SPA, other SPAs get tokens without a new login. The same applies when a SPA renews expired tokens.
SSO depends on the session that nevisProxy keeps for the browser. The session cookie of this SSO session must reach the authorization endpoint. It must not reach the token endpoint.
Preconditions
- nevisMeta is set up, see Setting up a nevisMeta Instance.
- An OAuth 2.0 Authorization Server is set up, see Setting up an OAuth 2.0 Authorization Server.
Why the token request must not carry the SSO session cookie
nevisAuth answers a token request as the client, not as the user. After the response, nevisAuth tells nevisProxy to end the session of the token request. This is intentional: a client does not need a session at the token endpoint.
A server-side client sends the token request without the cookies of the browser. The token request then has its own short-lived session, and only this session ends.
A SPA sends the token request from the browser with fetch. If the browser adds the SSO session cookie, the token request arrives in the SSO session of the user. nevisProxy does not accept the change from the user to the client in an authenticated session. It ends the SSO session and returns HTTP status 403. The SPA gets no tokens, and the next authorization request needs a new login.
For this reason, send the token request without the SSO session cookie.
Whether the browser sends the cookie depends on the credentials option of fetch and on the origins of the SPA and the token endpoint:
| SPA and token endpoint | credentials value | Cookie sent | Result |
|---|---|---|---|
Same origin, for example https://www.example.com/app and https://www.example.com/oauth/token | same-origin (default) or include | Yes | Token request fails, SSO session ends |
| Same origin | omit | No | Tokens, SSO session stays |
Different origins, for example https://app.example.com and https://login.example.com | same-origin (default) or omit | No | Tokens, SSO session stays |
| Different origins | include | Yes | Token request fails, SSO session ends |
The table applies when the SPA and nevisProxy are on the same site, for example both on example.com. On different sites, the SameSite attribute of the cookie can also stop the browser from sending it.
The Allowed Origins setting of the authorization server does not prevent the browser from sending the cookie. The browser sends a form-encoded token request without a preflight request. The CORS response headers then only control whether the SPA can read the response, and nevisProxy has already ended the session.
Choosing an approach
| Approach | Use when | What the SPA does |
|---|---|---|
| Backend for frontend (BFF) | You can run a server-side component for the SPA. | Calls its backend. The backend is a confidential client and sends the token request from the server. |
| Token request without credentials | The SPA is a public client and gets the tokens itself. | Sends the token request with credentials: 'omit'. On a different origin, the default credentials value also works. |
| Refresh tokens | The SPA must renew tokens without the SSO session. | Requests the offline_access scope and renews tokens with the refresh token. The refresh request needs no session. |
A BFF is the most secure approach, because the tokens never reach the browser. If the SPA gets the tokens itself, verify the credentials setting of your OpenID Connect client library. If the SPA uses refresh tokens, enable Refresh Token Rotation after confirming that every client on this authorization server handles rotated tokens, and store refresh tokens securely.
You can combine the approaches. For example, a SPA can send the token request without credentials and also use refresh tokens.
Configuring the authorization server
To let SPAs get tokens without a new login while the SSO session is active, enable the prompt parameter:
- In nevisAdmin 4, open the OAuth 2.0 Authorization Server / OpenID Provider pattern.
- In Custom Properties, add the key
openid.promptParameterSupportedwith the valuetrue. - Deploy the configuration.
Use prompt=none with nevisAuth 9.2605.1 or later, or with nevisAuth 8.2607 LTS. Earlier versions do not handle prompt=none without an active session correctly.
With this property set to true, nevisAuth handles the prompt parameter of the authorization request as follows:
prompt value | Active SSO session | No active SSO session |
|---|---|---|
none | Redirect to the client with a new authorization code. There is no login and no consent screen. The auth_time claim keeps the time of the original login. | Redirect to the client with the error login_required. |
login | Login screen | Login screen |
Other values, for example consent | Ignored | Ignored |
Without the property, nevisAuth ignores the prompt parameter. A prompt=none request without an active session then shows the login screen instead of returning login_required.
If you configure the AuthorizationServer AuthState without nevisAdmin 4, set the property directly, see AuthorizationServer.
Configuring the SPA client in nevisMeta
In the nevisMeta Web Console, configure the SPA client as follows. For the field descriptions, see Entities.
- Type: PUBLIC
- Token Endpoint Auth Method: NONE. A SPA cannot keep a client secret.
- PKCE Mode: s256-required
- Registered Scopes: openid, and offline_access if the SPA uses refresh tokens.
- Policies: do not enable Force re-authentication. It requires a login on every authorization request, also with an active SSO session.
If the scopes of the SPA require consent, the user sees the consent screen at the first interactive authorization request of each SPA.
For a BFF, configure the backend as a CONFIDENTIAL client with a client secret instead.
Troubleshooting SSO for SPAs
| Symptom | Cause | Solution |
|---|---|---|
The token request returns HTTP status 403 with the page Blocked Request, and the next authorization request needs a new login. The nevisProxy log contains received corrupted SecToken: changed userID,sessionID [AUTH-0031]. | The token request carried the SSO session cookie. In the developer tools of the browser, the token request has a Cookie header. | Send the token request without credentials, or use a BFF. |
A prompt=none request shows the login screen. | openid.promptParameterSupported is not set to true. | Add the property in Custom Properties. |
A prompt=none request in a hidden iframe always returns login_required. | The browser blocks the SSO session cookie in third-party context. This happens when the SPA and nevisProxy are on different sites. | Send the prompt=none request as a top-level redirect, host the SPA and nevisProxy on the same site, or use refresh tokens. |