Skip to content

Sign in with Ubikap

This recipe uses openid-client v6 for Node.js. Each step also gives the raw HTTP exchange, for any other library.

Terminal window
npm install openid-client
import * as client from 'openid-client';
const config = await client.discovery(
new URL('https://auth.external-integration.ubikap.dev'),
CLIENT_ID,
{
client_secret: CLIENT_SECRET,
// Ubikap signs ID tokens with ES512; openid-client expects RS256 unless told otherwise
id_token_signed_response_alg: 'ES512',
},
client.ClientSecretBasic(CLIENT_SECRET),
);

Raw HTTP: GET https://auth.external-integration.ubikap.dev/.well-known/openid-configuration.

const codeVerifier = client.randomPKCECodeVerifier();
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
const state = client.randomState();
const nonce = client.randomNonce();
// Keep these three in the member's server-side session until the callback
session.oidc = { codeVerifier, state, nonce };
const authorizationUrl = client.buildAuthorizationUrl(config, {
redirect_uri: REDIRECT_URI,
scope: 'openid office offline_access',
prompt: 'consent',
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state,
nonce,
});
response.redirect(authorizationUrl.href);
ParameterValue
client_idYour client id.
response_typecode
redirect_uriOne of your registered redirect URIs, exactly.
scopeopenid office offline_access — add email profile if your client is allowed them.
promptconsent — required to receive a refresh token.
code_challengeBase64url of the SHA-256 of the code verifier.
code_challenge_methodS256 — the only accepted method.
stateRandom value, checked at the callback.
nonceRandom value, checked in the ID token.
login_hintOptional: the member’s email, to pre-fill the login page.

The resulting URL looks like:

https://auth.external-integration.ubikap.dev/auth?client_id=…&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid+office+offline_access&prompt=consent&code_challenge=…&code_challenge_method=S256&state=…&nonce=…
  1. The member enters their email, then their password — or is sent to their office’s SSO.
  2. A consent page asks the member to open their office to your application. Only a member allowed to manage the office’s connectors can accept; others land on the Ubikap error page.
  3. The provider redirects to your redirect_uri with ?code=…&state=…, or with ?error=access_denied&state=… when the member refuses.
const tokens = await client.authorizationCodeGrant(config, new URL(request.url, APP_ORIGIN), {
pkceCodeVerifier: session.oidc.codeVerifier,
expectedState: session.oidc.state,
expectedNonce: session.oidc.nonce,
});
const { sub } = tokens.claims()!;

Raw HTTP:

POST /token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=…&redirect_uri=…&code_verifier=…

The response holds access_token (opaque, 1 hour), id_token (only sub), refresh_token and expires_in.

const userInfo = await client.fetchUserInfo(config, tokens.access_token, sub);
const officeKey = userInfo.office_key;

Raw HTTP: GET /me with Authorization: Bearer <access_token>. Response:

{
"sub": "jane.doe@example.com",
"office_key": "OFC_1234",
"email": "jane.doe@example.com",
"given_name": "Jane",
"family_name": "Doe",
"name": "Jane Doe"
}

email, given_name, family_name and name are present only with the email and profile scopes.

const refreshed = await client.refreshTokenGrant(config, storedRefreshToken);
// The previous refresh token is now invalid: persist the new one before anything else
await saveRefreshToken(memberId, refreshed.refresh_token);

Run at most one refresh per member at a time: reusing a refresh token revokes the whole sign-in. See OIDC tokens and refresh.

await client.tokenRevocation(config, storedRefreshToken, { token_type_hint: 'refresh_token' });
const endSessionUrl = client.buildEndSessionUrl(config, {
id_token_hint: storedIdToken,
});
response.redirect(endSessionUrl.href);

The member confirms the logout on a Ubikap page, then lands on the Ubikap login page. Do not send post_logout_redirect_uri: no client has one registered, so the provider would reject the request.

Revoking the refresh token is enough to sign the member out of your application only; ending the session also signs them out of Ubikap in this browser.

  • PKCE with S256 on every authorization request.
  • office in the scope, and office_key read from /me — never from the ID token.
  • offline_access and prompt=consent to get a refresh token.
  • ID tokens verified as ES512.
  • One refresh at a time per member, with the rotated refresh token persisted before use.