Corpland ID

Quickstart

Send people to Corpland ID, take them back on your callback, swap the code for tokens, refresh, and sign out.

The whole flow is four requests. The examples use the local addresses; swap in production ones when you deploy.

your app ──redirect──▶ id.corplandtechnologies.com/sign-in
                         (signs in, maybe picks a venture)
your app ◀──redirect── /auth/callback?code=...&state=...
your app ──POST /oauth/token──▶ ID API ──▶ access_token, refresh_token

1. Make a PKCE pair and a state

Do this in the browser right before you redirect. Keep both values in sessionStorage so the callback page can read them.

const base64url = (bytes: ArrayBuffer) =>
  btoa(String.fromCharCode(...new Uint8Array(bytes)))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");

export const createPkce = async () => {
  const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)).buffer);
  const challenge = base64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
  const state = base64url(crypto.getRandomValues(new Uint8Array(16)).buffer);
  return { verifier, challenge, state };
};

2. Redirect to the sign in page

const { verifier, challenge, state } = await createPkce();
sessionStorage.setItem("cid_pkce", JSON.stringify({ verifier, state }));

const url = new URL("http://localhost:3300/sign-in");
url.search = new URLSearchParams({
  client_id: "corpland-content",
  redirect_uri: "http://localhost:3320/auth/callback",
  state,
  code_challenge: challenge,
  code_challenge_method: "S256",
}).toString();

window.location.assign(url.toString());
ParameterRequiredNotes
client_idYesYour registered client id.
redirect_uriYesMust match a registered callback exactly. No wildcards.
stateYesRandom. You check it on the way back.
code_challengeYesSHA 256 of the verifier, base64url.
code_challenge_methodYesAlways S256.
promptNologin forces the password form even if they are signed in. select_venture asks them to pick or create a venture.
venture_hintNoA venture id or handle to preselect in the picker.

Already signed in people see "Continue as Ama" and one button. New people can create a Corpland ID on the same page and are returned to your app after they verify their email.

3. Swap the code on your callback

Check the state first, then call the token endpoint. Codes live 60 seconds and work once.

const params = new URLSearchParams(window.location.search);
const saved = JSON.parse(sessionStorage.getItem("cid_pkce") || "{}");
sessionStorage.removeItem("cid_pkce");

if (!params.get("code") || params.get("state") !== saved.state) {
  throw new Error("Sign in was interrupted. Try again.");
}

const response = await fetch("http://localhost:5008/api/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "authorization_code",
    code: params.get("code"),
    client_id: "corpland-content",
    redirect_uri: "http://localhost:3320/auth/callback",
    code_verifier: saved.verifier,
  }),
});
const tokens = await response.json();

The answer:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "Vb4qk...",
  "refresh_expires_at": "2026-11-09T12:00:00.000Z",
  "venture_id": "6aca9bfeb50f1fec97df56d6",
  "user": { "_id": "6aca9bf9b50f1fec97df56c0", "name": "Ama Mensah", "email": "ama@acme.com", "image": null },
  "message": "Signed in."
}

Send Authorization: Bearer <access_token> to your backend. venture_id is null unless you asked for prompt=select_venture.

4. Refresh before the access token ends

Access tokens last 15 minutes. Refresh tokens rotate: every refresh returns a new one and the old one stops working.

const refreshed = await fetch("http://localhost:5008/api/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "refresh_token",
    refresh_token: tokens.refresh_token,
    client_id: "corpland-content",
  }),
}).then((r) => r.json());

Two tabs refreshing at the same moment is fine: a rotated token is still accepted for 10 seconds. After that, presenting an old refresh token is treated as theft and the whole session family is ended with token_reused. Send the person back to step 2 when that happens.

Refresh sessions last 30 days from the last refresh and never more than 90 days from the first sign in.

5. Sign out

Revoke your refresh token, forget your tokens, then end the Corpland ID session too if you want a full sign out.

await fetch("http://localhost:5008/api/v1/oauth/revoke", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token: tokens.refresh_token, client_id: "corpland-content" }),
});

const out = new URL("http://localhost:3300/sign-out");
out.search = new URLSearchParams({
  client_id: "corpland-content",
  post_logout_redirect_uri: "http://localhost:3320/",
}).toString();
window.location.assign(out.toString());

post_logout_redirect_uri must be registered for your client, or the page signs the person out and stays on Corpland ID.

On this page