Skip to content

Quickstart Guide

This guide walks you through your first SingleSign integration end to end. By the final step you will have a working login flow that returns verified user identity data.

Prerequisites: A modern browser and Node.js 18+ (for running the samples). The code below is vanilla TypeScript — adapt it to your framework of choice.


Step 1 — Create a SingleSign Account

Sign up at singlesign.com to get access to the developer portal. After registration you will receive:

  • A Platform GUID (platform_guid) — passed as a query parameter on every OAuth request.
  • Access to the Developer Portal where you register applications.

Keep your Platform GUID handy; you'll use it in every API call.


Step 2 — Register Your Application

In the developer portal, create a new application:

  1. Click Create Application.
  2. Enter a display name (e.g., "My Web App").
  3. Add one or more Redirect URIs — for local development, use http://localhost:3000/callback.
  4. Select the scopes your app needs (start with openid profile email).
  5. Save the app and copy your Client ID (client_id).

If your app is a server-side web application, you will also receive a Client Secret. Keep it out of client-side code. See the Server Web App Guide.

// Save these values in your application config
const config = {
  clientId: "your-client-id",
  redirectUri: "http://localhost:3000/callback",
  platformGuid: "your-platform-guid",
  authorizationEndpoint: "https://api.singlesign.com/oauth/authorize",
  tokenEndpoint: "https://api.singlesign.com/oauth/token",
  userinfoEndpoint: "https://api.singlesign.com/oauth/userinfo",
};

Step 3 — Generate PKCE Code Verifier and Challenge

PKCE (Proof Key for Code Exchange) protects the authorization code exchange from interception attacks. You generate a random code_verifier, then derive a code_challenge using SHA-256.

/** Generate a cryptographically random code verifier (43–128 characters). */
function generateCodeVerifier(): string {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

/** Derive the S256 code challenge from the verifier. */
async function generateCodeChallenge(verifier: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest("SHA-256", data);
  return base64UrlEncode(new Uint8Array(digest));
}

/** Base64-URL encode a byte array (no padding). */
function base64UrlEncode(bytes: Uint8Array): string {
  let binary = "";
  bytes.forEach((b) => (binary += String.fromCharCode(b)));
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

Store the code_verifier in sessionStorage so it survives the redirect round-trip:

const codeVerifier = generateCodeVerifier();
sessionStorage.setItem("pkce_code_verifier", codeVerifier);

const codeChallenge = await generateCodeChallenge(codeVerifier);

Step 4 — Redirect the User to Authorize

Build the authorization URL and redirect the browser. Include a state parameter to prevent CSRF.

/** Generate a random state value for CSRF protection. */
function generateState(): string {
  const array = new Uint8Array(16);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

async function redirectToSingleSign(): Promise<void> {
  const codeVerifier = generateCodeVerifier();
  sessionStorage.setItem("pkce_code_verifier", codeVerifier);

  const codeChallenge = await generateCodeChallenge(codeVerifier);

  const state = generateState();
  sessionStorage.setItem("oauth_state", state);

  const params = new URLSearchParams({
    platform_guid: config.platformGuid,
    response_type: "code",
    client_id: config.clientId,
    redirect_uri: config.redirectUri,
    scope: "openid profile email",
    state: state,
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
  });

  // Redirect the browser to SingleSign
  window.location.href = `${config.authorizationEndpoint}?${params.toString()}`;
}

The user will see the SingleSign login screen, authenticate, and consent to the requested scopes. SingleSign then redirects back to your redirect_uri with an authorization code and the state you provided:

http://localhost:3000/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE

Step 5 — Exchange the Authorization Code for Tokens

On your callback page, verify the state, then exchange the code for tokens.

async function handleCallback(): Promise<void> {
  const params = new URLSearchParams(window.location.search);
  const code = params.get("code");
  const returnedState = params.get("state");

  // 1. Verify state matches what we stored
  const savedState = sessionStorage.getItem("oauth_state");
  if (returnedState !== savedState) {
    throw new Error("State mismatch — possible CSRF attack.");
  }

  // 2. Retrieve the stored code verifier
  const codeVerifier = sessionStorage.getItem("pkce_code_verifier");
  if (!codeVerifier) {
    throw new Error("Missing code verifier — restart the login flow.");
  }

  // 3. Exchange the code for tokens
  const tokenResponse = await fetch(config.tokenEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: code!,
      redirect_uri: config.redirectUri,
      client_id: config.clientId,
      code_verifier: codeVerifier,
      platform_guid: config.platformGuid,
    }).toString(),
  });

  if (!tokenResponse.ok) {
    const error = await tokenResponse.json();
    throw new Error(`Token exchange failed: ${error.error_description}`);
  }

  const tokens = await tokenResponse.json();
  // tokens contains: access_token, id_token, token_type, expires_in
  // and refresh_token if offline_access scope was requested

  console.log("Access Token:", tokens.access_token);
  console.log("ID Token:", tokens.id_token);

  // Clean up session storage
  sessionStorage.removeItem("pkce_code_verifier");
  sessionStorage.removeItem("oauth_state");

  // Store tokens securely (see security notes below)
  sessionStorage.setItem("access_token", tokens.access_token);
}

Token response example:

{
  "access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...",
  "id_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Step 6 — Fetch User Info

Use the access token to call the UserInfo endpoint and retrieve the authenticated user's claims.

async function getUserInfo(): Promise<Record<string, unknown>> {
  const accessToken = sessionStorage.getItem("access_token");
  if (!accessToken) {
    throw new Error("No access token available. Please log in first.");
  }

  const response = await fetch(config.userinfoEndpoint, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (!response.ok) {
    throw new Error(`UserInfo request failed: ${response.status}`);
  }

  const userInfo = await response.json();
  console.log("User GUID:", userInfo.human_guid);
  console.log("Email:", userInfo.email);
  console.log("Name:", userInfo.name);

  return userInfo;
}

UserInfo response example:

{
  "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "human_guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "email_verified": true
}

The human_guid claim is the unique, stable identifier for the user across all SingleSign-integrated applications. Use it as your primary user key.


Putting It All Together

// On your login page / button click:
document.getElementById("login-btn")?.addEventListener("click", () => {
  redirectToSingleSign();
});

// On your callback page (e.g., /callback):
(async () => {
  try {
    await handleCallback();
    const user = await getUserInfo();
    document.getElementById("welcome")!.textContent =
      `Welcome, ${user.name}! (${user.email})`;
  } catch (err) {
    console.error("Authentication failed:", err);
  }
})();

Security Best Practices

  • Always use PKCE — even for server-side apps, PKCE adds defense in depth.
  • Validate the state parameter on every callback to prevent CSRF.
  • Store tokens in sessionStorage (or a secure HTTP-only cookie for server apps) — never localStorage for sensitive tokens.
  • Use short-lived access tokens and refresh them with offline_access when the user is away.
  • Register exact redirect URIs — no wildcards allowed.

Next Steps

Last updated: 2026-02-16