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
- Register your application with your list of redirect URIs to get
client_idandclient_secret - Redirect users to our authorization endpoint
- Receive authorization code via callback to one of your registered URIs
- Exchange code for tokens at our token endpoint
- 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
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code |
client_id | Yes | Your application's client ID |
redirect_uri | Yes | Must exactly match one of your registered redirect URIs |
scope | Yes | Space-separated scopes (e.g., openid profile email) |
state | Recommended | Random string for CSRF protection |
nonce | Optional | Random string for ID token replay protection |
code_challenge | Recommended* | PKCE code challenge (see PKCE section) |
code_challenge_method | Recommended* | 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=S256What Happens Next
- User is redirected to authenticate (if not already logged in)
- User approves access to your application (if required)
- User is redirected back to your
redirect_uriwith 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
| Parameter | Description |
|---|---|
code | Authorization code (single-use, expires in 10 minutes) |
state | The state value you provided (verify it matches) |
Error Response
https://yourapp.com/callback?
error=invalid_scope&
error_description=Invalid+scopes&
state=random_state_string
| Parameter | Description |
|---|---|
error | Error code (see Error Codes section) |
error_description | Human-readable error description |
state | The 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"
}| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be authorization_code |
code | Yes | Authorization code from callback |
client_id | Yes | Your client ID |
client_secret | Yes* | Your client secret |
redirect_uri | Yes | Must exactly match the authorization request |
code_verifier | Conditional** | 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"
}| Field | Description |
|---|---|
access_token | Use this to access protected resources (JWT format) |
token_type | Always Bearer |
expires_in | Token lifetime in seconds (typically 3600 = 1 hour) |
refresh_token | Use this to get new access tokens without re-authentication |
id_token | JWT containing user identity claims (if openid scope requested) |
scope | Granted 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:
| Scope | Claims Included |
|---|---|
openid | sub (user ID) - Required for OpenID Connect |
profile | name, firstName, lastName |
email | email, 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
| Scope | Description | Claims Provided |
|---|---|---|
openid | Required for OpenID Connect authentication | sub |
profile | Access to user profile information | name, firstName, lastName |
email | Access to user email address | email, email_verified |
Custom Application Scopes
These claims are always included regardless of scope:
workerProfileId- Worker profile identifieruserIds[]- 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=S2564. 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
| Claim | Description |
|---|---|
iss | Issuer (our authorization server) |
sub | Subject (unique user identifier) |
aud | Audience (your client_id) |
exp | Expiration time (Unix timestamp) |
iat | Issued at (Unix timestamp) |
nonce | Your nonce value (if provided) |
Validating the ID Token
- Verify signature using our public keys (JWT verification)
- Verify
issmatches our issuer URL - Verify
audcontains yourclient_id - Verify
expis in the future (not expired) - Verify
noncematches what you sent (if used)
Error Codes
Authorization Errors (Redirect)
| Error Code | Description |
|---|---|
invalid_request | Missing or invalid parameters |
unauthorized_client | Client not authorized for this request |
access_denied | User denied authorization |
unsupported_response_type | Server doesn't support the requested response type |
invalid_scope | Requested scope is invalid or not allowed |
server_error | Internal server error |
Token Endpoint Errors (JSON)
| Error Code | Description |
|---|---|
invalid_request | Missing or invalid parameters |
invalid_client | Client authentication failed |
invalid_grant | Authorization code invalid, expired, or already used |
unauthorized_client | Client not authorized for this grant type |
unsupported_grant_type | Grant type not supported |
invalid_scope | Requested scope is invalid |
UserInfo Endpoint Errors (JSON)
| Error Code | HTTP Status | Description |
|---|---|---|
invalid_token | 401 | Access token is invalid or expired |
insufficient_scope | 403 | Token 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*Updated about 6 hours ago
