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:
- Click Create Application.
- Enter a display name (e.g., "My Web App").
- Add one or more Redirect URIs — for local development, use
http://localhost:3000/callback. - Select the scopes your app needs (start with
openid profile email). - 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
stateparameter on every callback to prevent CSRF. - Store tokens in
sessionStorage(or a secure HTTP-only cookie for server apps) — neverlocalStoragefor sensitive tokens. - Use short-lived access tokens and refresh them with
offline_accesswhen the user is away. - Register exact redirect URIs — no wildcards allowed.
Next Steps
- Register an App — Detailed walkthrough of the developer portal.
- SPA with PKCE Guide — Production-ready SPA integration.
- Endpoint Reference — Full API documentation with curl examples.
- Tokens & Claims — Understand what's inside your tokens.
Last updated: 2026-02-16
Related docs
- SingleSign Developer DocumentationLearn how to integrate SingleSign identity and authentication into your applications using OAuth 2.0 and OpenID Connect.
- SaaS single sign-onSSO for SaaS applications using OAuth 2.0 and OpenID Connect: the flow to pick, the scopes to request, and a working integration path with SingleSign.
- React authentication with PKCEReact authentication done the recommended way: Authorization Code with PKCE, and no client secret in your JavaScript bundle.
- Mobile app authenticationMobile app authentication for iOS and Android: Authorization Code with PKCE in a system browser, custom scheme redirects, and secure token storage on device.
- Firebase Auth comparisonSingleSign vs Firebase Authentication on lock-in, portability and consent, plus the cases where staying on Firebase is right.
- Auth0 comparisonSingleSign vs Auth0 compared on the difference that matters: who owns the account. A side-by-side table, then the three cases where each one is the right call.
- authentication for startupsAuthentication for startups without a monthly-active-user meter: standards-based OAuth 2.0 sign-in you will not have to replace when the product finds traction.
- the SPA with PKCE docsComplete guide to integrating SingleSign into a single-page application using the Authorization Code flow with PKCE.