OpenID Connect Sign-In for Applications
When an operator has configured OpenID Connect sign-in, a user can sign in to the REST Gateway with their organization's identity provider. The Gateway validates the provider's answer itself and opens a session acting as the key-server principal the operator mapped the user to. Every operation on that session is authorized by the key server, as for any other session.
There are two ways to use it:
- The Gateway's own login page, at
/oidc/on the Gateway. Nothing to build. - Your own page, driving the five routes yourself.
The login page
The operator gives users a link of this form:
https://gateway.example.test/oidc/?provider=<providerId>&binding=<bindingId>
The page is plain HTML and one script, served by the Gateway from its own origin under a strict Content Security Policy. It talks to nothing but the Gateway. It holds no secret and sets no cookie.
What the user sees
The page is always in one of five states (the data-state of its #te-oidc element):
| State | The user sees |
|---|---|
idle | Sign in through your identity provider. and a Sign in button. This is also where the page returns after a sign-out, an ended session, or a sign-in window closed early. |
at-provider | Continue in the sign-in window. A separate window shows the identity provider's own login page, where the user signs in as usual. |
minting | Signed in at the provider. Creating your session. This lasts a moment. |
signed-in | Signed in., the principal the user is acting as, when the session ends, and a Sign out button. |
refused | The sign-in was refused. and a code, such as identity_not_mapped. The code is the Gateway's public refusal class; the operator can look up the reason in the Gateway's log. |
The sign-in window must be allowed to open: if the browser blocks it, the page shows
POPUP_BLOCKED. A link without both provider and binding shows PAGE_MISCONFIGURED.
A reload or a closed tab signs you out
The page keeps the session in its own memory only: not in local or session storage, not in IndexedDB, not in a cookie. Reloading the page, navigating away from it or closing the tab signs the user out of the page. They select Sign in again; if their identity provider still has them signed in, that takes a moment and no password.
The session is bound to a signing key that the page made in the browser and that cannot be exported. Every request on the session carries a proof signed with that key. When the page goes, the key goes with it, so the session can no longer be used by anyone. It does stay on the Gateway until its idle or absolute limit. To end it at once, select Sign out, which deletes it on the Gateway.
A session also ends by itself after the binding's absolute limit, or after it has been idle
for the binding's idle limit. The page returns to idle with Your session reached its end.
Calling the API from the page: teOidc.fetch
The page exposes one object, window.teOidc, to scripts running in its document:
| Member | What it does |
|---|---|
teOidc.fetch(path, init) | Sends a request on the session. path is a path on the Gateway, beginning with /. init takes method, body (a string) and headers. It adds Authorization: Bearer and a fresh DPoP proof signed by the page's key over the method, the URL, the bearer and the exact body bytes. It returns the fetch Response. |
teOidc.signOut() | Deletes the session on the Gateway (DELETE /api/v1/session) and returns to idle. |
teOidc.state() | The current state, one of the five above. |
teOidc.key | The page's key pair while signed in, otherwise null. Its private key is non-extractable: exporting it is refused by the browser. |
For example, to encrypt under a key the user may use:
const r = await teOidc.fetch('/api/v1/crypto/encrypt', {
method: 'POST',
body: JSON.stringify({ data: btoa('hello'), ttag: '<the key>' }),
});
A denied operation is answered by the key server, for example 403 ACL_DENIED, and the page
shows the last call and its result.
Writing your own page
A team that needs its own user interface drives the same five routes the Gateway's page does. They are open routes, each guarded by single-use evidence bound to one browser:
POST /api/v1/auth/oidc/start. Make a non-extractable P-256 key withcrypto.subtle.generateKeyand a random redemption secret. SendproviderId,bindingId, the key's public JWK (kty,crv,x,y) assigningJwk, and the SHA-256 of the secret, base64url, asredemptionChallenge. The answer carriesflowIdandlaunchUrl. The request must come from the binding'sapplicationOrigin(403 ORIGIN_REJECTEDotherwise).GET /api/v1/auth/oidc/launch. OpenlaunchUrlin a new window. Open the window inside the user's click, before anyawait, or the browser blocks it. The Gateway sets the one transaction cookie and redirects to the provider.GET /api/v1/auth/oidc/callback/{providerId}. The provider redirects the window here. The Gateway exchanges the code, validates the ID token and maps the user. Its completion page then posts{te: 'oidc', flowId, ok}to the opener, witherrorset to a public code whenokis false. Accept the message only whenevent.originis your origin,event.sourceis the window you opened andflowIdis yours.POST /api/v1/auth/oidc/challenge. SendflowIdand the redemption secret; the answer is a one-timenonce.POST /api/v1/session/oidc. SendflowId,redemptionSecretandsigningJwk. Add aTE-SSO-Mintheader: an ES256 JWS signed with your key, headertyp: te-sso-mint+jwtwith the public JWK, and claimsmth: 'oidc',org(your origin),htm: 'POST',nonce,flow(the flowId),iat,jtiandbth(the SHA-256 of the exact body bytes you send). The answer carries the session bearer, the principal it acts as (actingAs) andexpiresAt.
From then on, send every request with Authorization: Bearer <session> and a DPoP proof
signed by the same key, as Integrating via the API describes.
Sign out with DELETE /api/v1/session.
Keep the bearer and the key in memory. Do not put either in storage or in a cookie, and do not put the bearer, a proof, the secret, the nonce or the flowId into the page's text, its console or a URL you build.
If your page is served with Referrer-Policy: no-referrer, set referrerPolicy: 'strict-origin' on your own fetch calls. Under no-referrer a browser sends Origin: null
on a same-origin POST, and /start refuses it with ORIGIN_REJECTED.
Related pages
- OpenID Connect Sign-In: the operator's configuration and the refusal codes.
- Configuring Keycloak: the provider side.
- Delegated Authentication: how the session acts as the mapped principal.
- Sessions in the API reference: every session route.