Quick Start Guide
API Version: 5.24.0
⚠️ OAuth 2.0 Authorization Code Flow Required
This SSO uses standard OAuth 2.0. No client library needed - implement OAuth flow in your backend.
1. Register Your Application
Login to SSO Admin Panel, navigate to OAuth Clients, and click + New Client.
Required Information:
- Application name (e.g., "Launchmass", "Messmass")
- Application description
- Redirect URIs (development and production)
- Required scopes (e.g.,
openid profile email offline_access) - Homepage URL
You will receive:
{
"client_id": "your-client-uuid",
"client_secret": "your-secret-uuid"
}⚠️ Store client_secret securely! It will only be shown once.
Note: PKCE (Proof Key for Code Exchange) is recommended for enhanced security. See Authentication Guide for PKCE implementation.
2. Redirect Users to SSO Login
When users click "Login", redirect them to the SSO authorization endpoint with PKCE parameters:
// In your frontend (any framework)
async function handleLogin() {
// Generate PKCE parameters
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store verifier for token exchange
sessionStorage.setItem('pkce_verifier', codeVerifier);
const authUrl = new URL('https://sso.doneisbetter.com/api/oauth/authorize');
authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/auth/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile email offline_access');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', generateRandomState()); // CSRF protection
// Redirect user to SSO
window.location.href = authUrl.toString();
}
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
async function generateCodeChallenge(verifier) {
const data = new TextEncoder().encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}Parameters:
client_id- Your OAuth client ID (from step 1)redirect_uri- Where SSO redirects after login (must match registered URI)response_type- Alwayscodefor authorization code flowscope- Requested scopes (space-separated, includeoffline_accessfor refresh tokens)code_challenge- PKCE challenge (SHA-256 hash of code_verifier)code_challenge_method- AlwaysS256state- Random string for CSRF protectionprompt- (Optional) Controls auth behavior:login- Force re-authentication (use on logout to require credentials)consent- Force consent screennone- No UI, fail if interaction required
Authentication Options: Users can login with email/password, magic link (passwordless), or social providers (Facebook, Google coming soon).
3. Handle OAuth Callback (Backend)
After user authenticates, SSO redirects to your redirect_uri with an authorization code:
https://yourapp.com/auth/callback?code=AUTHORIZATION_CODE
Exchange the code for tokens (server-side only!):
// Node.js / Express example
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
// Validate state parameter (CSRF protection)
if (state !== req.session.oauth_state) {
return res.status(400).send('Invalid state parameter');
}
// Retrieve PKCE verifier from session
const codeVerifier = req.session.pkce_verifier;
// WHAT: Exchange authorization code for tokens
// WHY: Code is single-use and short-lived, need long-lived tokens
const tokenResponse = await fetch('https://sso.doneisbetter.com/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: code,
client_id: process.env.SSO_CLIENT_ID,
client_secret: process.env.SSO_CLIENT_SECRET,
redirect_uri: 'https://yourapp.com/auth/callback',
code_verifier: codeVerifier // PKCE verification
})
});
const tokens = await tokenResponse.json();
// tokens = { access_token, refresh_token, id_token, expires_in }
// WHAT: Decode ID token to get user info
// WHY: ID token contains user identity (works for all login methods)
const jwt = require('jsonwebtoken');
const userInfo = jwt.decode(tokens.id_token);
// userInfo = { sub, email, name, email_verified, picture }
// WHAT: Create your app's session
// WHY: Store user identity and tokens for future API calls
req.session.userId = userInfo.sub;
req.session.email = userInfo.email;
req.session.name = userInfo.name;
req.session.accessToken = tokens.access_token;
req.session.refreshToken = tokens.refresh_token;
// Redirect to your app
res.redirect('/dashboard');
});4. Validate User Sessions
Before serving protected content, validate the user's session:
// Middleware to protect routes
async function requireAuth(req, res, next) {
const accessToken = req.session.accessToken;
if (!accessToken) {
return res.redirect('/login');
}
// WHAT: Validate access token with SSO
// WHY: Ensure user still has approved access (not revoked)
const validation = await fetch('https://sso.doneisbetter.com/api/public/session', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!validation.ok) {
// Token invalid or expired - try refresh
const refreshed = await refreshAccessToken(req.session.refreshToken);
if (!refreshed) {
return res.redirect('/login');
}
req.session.accessToken = refreshed.access_token;
}
next();
}
// Use middleware
app.get('/dashboard', requireAuth, (req, res) => {
res.render('dashboard', { user: req.session });
});5. Refresh Access Tokens
Access tokens expire after 1 hour. Use refresh tokens to get new ones:
async function refreshAccessToken(refreshToken) {
const response = await fetch('https://sso.doneisbetter.com/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: process.env.SSO_CLIENT_ID,
client_secret: process.env.SSO_CLIENT_SECRET
})
});
if (!response.ok) {
return null; // Refresh token invalid or expired
}
return await response.json();
// Returns { access_token, refresh_token, expires_in }
}6. Implement Logout
Revoke tokens and clear session (both your app and SSO):
app.post('/logout', async (req, res) => {
const refreshToken = req.session.refreshToken;
// WHAT: Revoke refresh token at SSO
// WHY: Invalidate all tokens (access tokens become invalid too)
await fetch('https://sso.doneisbetter.com/api/oauth/revoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: refreshToken,
token_type_hint: 'refresh_token',
client_id: process.env.SSO_CLIENT_ID,
client_secret: process.env.SSO_CLIENT_SECRET
})
});
// Clear your app's session
req.session.destroy();
// WHAT: Redirect to SSO logout endpoint
// WHY: Clear SSO cookie session across all apps
const logoutUrl = 'https://sso.doneisbetter.com/api/oauth/logout' +
'?post_logout_redirect_uri=' + encodeURIComponent('https://yourapp.com');
res.redirect(logoutUrl);
});Note: The post_logout_redirect_uri must match a registered redirect URI for your OAuth client.
Security Tip: After logout, when redirecting user back to login, add prompt=login parameter to force re-authentication:
// After logout redirect const loginUrl = 'https://sso.doneisbetter.com/api/oauth/authorize' + '?client_id=' + encodeURIComponent(CLIENT_ID) + '&redirect_uri=' + encodeURIComponent(REDIRECT_URI) + '&response_type=code' + '&scope=openid profile email offline_access' + '&state=' + generateRandomState() + '&code_challenge=' + codeChallenge + '&code_challenge_method=S256' + '&prompt=login'; // Force re-authentication window.location.href = loginUrl;
Next Steps
You now have basic OAuth 2.0 integration! Continue with:
- Authentication Guide - Deep dive into OAuth flow
- App Permissions - Understanding approval workflow
- Admin Approval - How users get access
- API Reference - Complete endpoint documentation
- Security Best Practices
Common Issues
- "Access Pending Approval"
User registered but SSO admin hasn't approved access yet. Check Admin Approval Guide.
- "Redirect URI Mismatch"
The redirect_uri parameter must exactly match a registered URI. Check with SSO admins.
- "Invalid Client Credentials"
client_id or client_secret is wrong. Verify environment variables.
- Token expired errors
Implement token refresh (step 5) to handle expiration gracefully.