OAuth2.0 & SSO

OpenID Connect & OAuth 2.0 Implementation Documentation

OAuth 2.0 & OpenID Connect Integration Guide

Overview

This guide helps you integrate with our OAuth 2.0 and OpenID Connect (OIDC) authentication system. You'll be able to authenticate users and access their profile information securely.

Base URL: https://api.dashboard.listoglobal.com

Demo URL: https://api.dashboard.demo.listoglobal.com


Quick Start

  1. Register your application with your list of redirect URIs to get client_id and client_secret
  2. Redirect users to our authorization endpoint
  3. Receive authorization code via callback to one of your registered URIs
  4. Exchange code for tokens at our token endpoint
  5. Access user information using the access token

Note: Each OAuth client is configured for a specific Application Instance. Users will authenticate through that instance's authentication system.


Prerequisites

Before integrating, you'll need:

  • Client ID - Your application identifier
  • Client Secret - Your application secret (for confidential clients)
  • Redirect URIs - List of pre-registered callback URLs for your application
  • Allowed Scopes - Permissions granted to your application
  • Application Instance - The specific instance your client is configured for

Important Notes

  • Multiple Redirect URIs: You can register multiple redirect URIs for different environments (development, staging, production). Each must be explicitly registered during client setup.
  • Multiple Clients: Your organization can have multiple OAuth clients. Each client is configured for a specific Application Instance and can have different settings, scopes, and redirect URIs.
  • Instance-Specific Configuration: Each SSO client is tied to a specific Application Instance. Users will authenticate through that instance's authentication system.

Contact your administrator to register your application and provide your list of redirect URIs.


Step 1: Authorization Request

Redirect the user to our authorization endpoint to begin authentication.

Endpoint

GET https://api.dashboard.listoglobal.com/auth/authorize

Parameters

ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour application's client ID
redirect_uriYesMust exactly match one of your registered redirect URIs
scopeYesSpace-separated scopes (e.g., openid profile email)
stateRecommendedRandom string for CSRF protection
nonceOptionalRandom string for ID token replay protection
code_challengeRecommended*PKCE code challenge (see PKCE section)
code_challenge_methodRecommended*Must be S256

*Required if your client is configured to require PKCE

Note: The redirect_uri must be an exact match (including protocol, domain, path, and port) with one of the URIs you registered during client setup.

Example Request

GET /auth/authorize?
  response_type=code&
  client_id=client_abc123&
  redirect_uri=https://yourapp.com/callback&
  scope=openid%20profile%20email&
  state=random_state_string&
  code_challenge=CHALLENGE_STRING&
  code_challenge_method=S256

What Happens Next

  1. User is redirected to authenticate (if not already logged in)
  2. User approves access to your application (if required)
  3. User is redirected back to your redirect_uri with an authorization code

Step 2: Handle Authorization Callback

After authentication, the user is redirected back to your application.

Success Response

https://yourapp.com/callback?
  code=AUTHORIZATION_CODE&
  state=random_state_string
ParameterDescription
codeAuthorization code (single-use, expires in 10 minutes)
stateThe state value you provided (verify it matches)

Error Response

https://yourapp.com/callback?
  error=invalid_scope&
  error_description=Invalid+scopes&
  state=random_state_string
ParameterDescription
errorError code (see Error Codes section)
error_descriptionHuman-readable error description
stateThe state value you provided

Important Security Check

⚠️ Always verify the state parameter matches what you sent to prevent CSRF attacks.


Step 3: Exchange Code for Tokens

Exchange the authorization code for access tokens by calling our token endpoint.

Endpoint

POST https://api.dashboard.listoglobal.com/auth/token

Headers

Content-Type: application/json

Request Body

{
  "grant_type": "authorization_code",
  "code": "AUTHORIZATION_CODE",
  "client_id": "client_abc123",
  "client_secret": "YOUR_CLIENT_SECRET",
  "redirect_uri": "https://yourapp.com/callback",
  "code_verifier": "PKCE_VERIFIER"
}
ParameterRequiredDescription
grant_typeYesMust be authorization_code
codeYesAuthorization code from callback
client_idYesYour client ID
client_secretYes*Your client secret
redirect_uriYesMust exactly match the authorization request
code_verifierConditional**PKCE code verifier

*Required for confidential clients
**Required if PKCE was used in authorization request

Important: The redirect_uri must be identical to the one used in the authorization request.

Success Response (200 OK)

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "refresh_abc123...",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}
FieldDescription
access_tokenUse this to access protected resources (JWT format)
token_typeAlways Bearer
expires_inToken lifetime in seconds (typically 3600 = 1 hour)
refresh_tokenUse this to get new access tokens without re-authentication
id_tokenJWT containing user identity claims (if openid scope requested)
scopeGranted scopes

Important: Decode the id_token to get the user claims used to identify the user.

Error Response (400 Bad Request)

{
  "error": "invalid_grant",
  "error_description": "Authorization code expired"
}

Step 4: Access User Information

Use the access token to retrieve user profile information.

Endpoint

GET https://api.dashboard.listoglobal.com/auth/userinfo

Headers

Authorization: Bearer YOUR_ACCESS_TOKEN

Success Response (200 OK)

{
  "sub": "user-unique-id",
  "email": "[email protected]",
  "email_verified": true,
  "name": "John Doe",
  "firstName": "John",
  "lastName": "Doe",
  "workerProfileId": "worker-123",
  "userIds": [
    {
      "id": "user-789",
      "clientId": "client-xyz"
    }
  ]
}

Claims by Scope

The information returned depends on the scopes granted:

ScopeClaims Included
openidsub (user ID) - Required for OpenID Connect
profilename, firstName, lastName
emailemail, email_verified
(always included)workerProfileId, workerIds, userIds

Error Response (401 Unauthorized)

{
  "error": "invalid_token",
  "error_description": "Access token expired"
}

Refreshing Access Tokens

When your access token expires, use the refresh token to obtain a new one without requiring user re-authentication.

Endpoint

POST https://api.dashboard.listoglobal.com/auth/token

Request Body

{
  "grant_type": "refresh_token",
  "refresh_token": "YOUR_REFRESH_TOKEN",
  "client_id": "client_abc123",
  "client_secret": "YOUR_CLIENT_SECRET"
}

Success Response (200 OK)

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "refresh_new123...",
  "scope": "openid profile email"
}

Note: A new refresh token may be issued. Always update your stored refresh token.


Understanding Scopes

Scopes define what information your application can access.

Standard Scopes

ScopeDescriptionClaims Provided
openidRequired for OpenID Connect authenticationsub
profileAccess to user profile informationname, firstName, lastName
emailAccess to user email addressemail, email_verified

Custom Application Scopes

These claims are always included regardless of scope:

  • workerProfileId - Worker profile identifier
  • userIds[] - Array of user records with client associations

Example: Request openid profile email to get full user information.


PKCE (Proof Key for Code Exchange)

PKCE adds extra security, especially for public clients (mobile apps, SPAs). Some clients may require PKCE.

How to Implement PKCE

1. Generate Code Verifier

Create a random string (43-128 characters):

// JavaScript example
const codeVerifier = base64URLEncode(crypto.randomBytes(32));
// Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

2. Generate Code Challenge

Hash the code verifier:

const codeChallenge = base64URLEncode(
  crypto.createHash('sha256').update(codeVerifier).digest()
);

3. Send Challenge in Authorization Request

GET /auth/authorize?
  ...
  code_challenge=CODE_CHALLENGE&
  code_challenge_method=S256

4. Send Verifier in Token Request

{
  "grant_type": "authorization_code",
  ...
  "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}

The server will verify: SHA256(code_verifier) === code_challenge


ID Token (JWT)

If you request the openid scope, you'll receive an ID token containing user identity claims.

ID Token Structure

{
  "iss": "https://api.dashboard.listoglobal.com",
  "sub": "user-unique-id",
  "aud": "client_abc123",
  "exp": 1701238167,
  "iat": 1701234567,
  "nonce": "your-nonce-value",
  "client_id": "client_abc123",
  "scope": "openid profile email"
}

Important Claims

ClaimDescription
issIssuer (our authorization server)
subSubject (unique user identifier)
audAudience (your client_id)
expExpiration time (Unix timestamp)
iatIssued at (Unix timestamp)
nonceYour nonce value (if provided)

Validating the ID Token

  1. Verify signature using our public keys (JWT verification)
  2. Verify iss matches our issuer URL
  3. Verify aud contains your client_id
  4. Verify exp is in the future (not expired)
  5. Verify nonce matches what you sent (if used)

Error Codes

Authorization Errors (Redirect)

Error CodeDescription
invalid_requestMissing or invalid parameters
unauthorized_clientClient not authorized for this request
access_deniedUser denied authorization
unsupported_response_typeServer doesn't support the requested response type
invalid_scopeRequested scope is invalid or not allowed
server_errorInternal server error

Token Endpoint Errors (JSON)

Error CodeDescription
invalid_requestMissing or invalid parameters
invalid_clientClient authentication failed
invalid_grantAuthorization code invalid, expired, or already used
unauthorized_clientClient not authorized for this grant type
unsupported_grant_typeGrant type not supported
invalid_scopeRequested scope is invalid

UserInfo Endpoint Errors (JSON)

Error CodeHTTP StatusDescription
invalid_token401Access token is invalid or expired
insufficient_scope403Token doesn't have required scopes

Complete Integration Example

Step-by-Step Flow

// 1. Generate PKCE values
const codeVerifier = generateRandomString(43);
const codeChallenge = base64URLEncode(sha256(codeVerifier));

// 2. Store state and code_verifier in session
const state = generateRandomString(32);
session.store('oauth_state', state);
session.store('code_verifier', codeVerifier);

// 3. Redirect user to authorization endpoint
const authUrl = 'https://api.dashboard.listoglobal.com/auth/authorize?' +
  'response_type=code&' +
  'client_id=client_abc123&' +
  'redirect_uri=https://yourapp.com/callback&' +
  'scope=openid%20profile%20email&' +
  'state=' + state;

redirect(authUrl);

// 4. Handle callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Verify state
  if (state !== session.get('oauth_state')) {
    throw new Error('Invalid state - possible CSRF attack');
  }
  
  // Exchange code for tokens
  const tokenResponse = await fetch('https://api.dashboard.listoglobal.com/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      code: code,
      client_id: 'client_abc123',
      client_secret: 'YOUR_CLIENT_SECRET',
      redirect_uri: 'https://yourapp.com/callback',
      code_verifier: session.get('code_verifier')
    })
  });
  
  const tokens = await tokenResponse.json();
  
  // Store tokens securely
  session.store('access_token', tokens.access_token);
  session.store('refresh_token', tokens.refresh_token);
  
  // Decode the tokens.id_token and get the email, and user claims
  // OR
  // Fetch user info
  const userInfoResponse = await fetch('https://api.dashboard.listoglobal.com/auth/userinfo', {
    headers: {
      'Authorization': 'Bearer ' + tokens.access_token
    }
  });
  
  const userInfo = await userInfoResponse.json();
  
  // User is now authenticated!
  console.log('User:', userInfo);
});

// 5. Refresh token when needed
async function refreshAccessToken() {
  const refreshToken = session.get('refresh_token');
  
  const response = await fetch('https://api.dashboard.listoglobal.com/auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: 'client_abc123',
      client_secret: 'YOUR_CLIENT_SECRET'
    })
  });
  
  const tokens = await response.json();
  session.store('access_token', tokens.access_token);
  session.store('refresh_token', tokens.refresh_token);
}

---

## Security Best Practices

### ✅ Do's

1. **Use HTTPS** for all requests in production
2. **Validate the `state` parameter** in callbacks to prevent CSRF attacks
3. **Implement PKCE** for public clients (SPAs, mobile apps)
4. **Store tokens securely**:
   - Backend: Encrypted session storage or httpOnly cookies
   - Mobile: Secure keychain/keystore
   - Never store in localStorage for web apps
5. **Validate ID tokens** before trusting the claims
6. **Refresh tokens proactively** before they expire
7. **Revoke tokens** when user logs out
8. **Use short-lived access tokens** (default: 1 hour)
9. **Handle token expiration** gracefully with refresh tokens

### ❌ Don'ts

1. **Never expose `client_secret`** in public clients (JavaScript, mobile apps)
2. **Never share tokens** between users or sessions
3. **Never log tokens** in plain text
4. **Don't skip `state` validation** - critical for security
5. **Don't store tokens** in localStorage (web) - use httpOnly cookies
6. **Don't ignore token expiration** - handle gracefully

---

## Client Types

### Confidential Clients (Server-Side Applications)

**Examples**: Traditional web apps with backend servers

**Characteristics**:
- Can securely store `client_secret`
- Required to authenticate with `client_secret` on token requests
- PKCE is optional but recommended

### Public Clients (Client-Side Applications)

**Examples**: Single Page Apps (SPAs), mobile apps, desktop apps

**Characteristics**:
- Cannot securely store `client_secret`
- Must use PKCE for security
- Token requests don't require `client_secret`

---

## Token Lifetimes

| Token Type | Default Lifetime | Renewable |
|-----------|------------------|-----------|
| Authorization Code | 10 minutes | No (single-use) |
| Access Token | 1 hour | Via refresh token |
| Refresh Token | 30 days | Yes (rolling) |
| ID Token | 1 hour | Via refresh token |

**Note**: Your client may have custom token lifetimes. Check with your administrator.

---

## Support

For integration support or to register your application, contact your system administrator or account manager.

## Standards & Compliance

This OAuth 2.0/OIDC implementation follows these standards:

- [OAuth 2.0 (RFC 6749)](https://tools.ietf.org/html/rfc6749)
- [OAuth 2.0 PKCE (RFC 7636)](https://tools.ietf.org/html/rfc7636)
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
- [JSON Web Token (JWT) (RFC 7519)](https://tools.ietf.org/html/rfc7519)

---

*Last updated: December 2025*