@endevre/id-client
v0.1.24
Published
Endevre ID client-side authentication library for SSO token exchange and session management.
Downloads
543
Readme
Endevre ID Client Auth
Client-side authentication helper for Endevre ID SSO flows.
Install
# Latest stable version
npm install @endevre/id-client
# or
yarn add @endevre/id-client
# Beta version
npm install @endevre/id-client@beta
# Development version
npm install @endevre/id-client@devCreating the client
import endevreID from '@endevre/id-client';
const client = endevreID.createClient('<clientID>', '<secret>', {
// Optional cookie configuration
cookiePrefix: 'endevre_id_', // default
cookieDomain: '.example.com', // optional
cookieSecure: true, // default inferred from https
cookieSameSite: 'Lax', // 'Lax' | 'Strict' | 'None' (default 'Lax')
defaultOrigin: 'https://app.example.com',
defaultReturnURL: 'https://app.example.com/logout', // used when window.origin unavailable
onRedirect: (url) => console.log('Redirect to', url)
});clientID and secret come from Endevre ID. Options control cookie behavior and can be omitted.
Browser-only client
If you only need to build login URLs or set browser cookies, you can instantiate a lightweight browser client that does not require the client secret:
const browserClient = endevreID.createBrowserClient('<clientID>');
const loginUrl = browserClient.createAuthUrl({
finalredirecturi: 'https://app.example.com/after-login',
redirecturis: ['https://app.example.com/start']
});
// Use server-issued tokens to set cookies and optionally control redirect handling
browserClient.setClientCookies(resultFromServer, 'pairing-id', {
shouldRedirect: true,
onRedirect: (url) => {
// Called when running outside the browser
console.log('Forward user to', url);
}
});Server-side usage
exchange, refreshToken, and getUserInfo can now run in non-browser environments (such as Node.js). When no browser is available, be sure to pass the required tokens explicitly:
// In a server context, provide the code yourself and include an origin header
const result = await client.exchange('authorization-code-from-callback', {
origin: 'https://app.example.com'
});
// Likewise for refresh token operations
const refreshed = await client.refreshToken('stored-refresh-token', {
origin: 'https://app.example.com'
});Generate login URL
const url = client.createAuthUrl({
finalredirecturi: 'https://app.example.com/after-login',
redirecturis: [
'https://app.example.com/start',
'https://app.example.com/middle'
],
// pairingID: 'optional-external-id', // optionally provide pairingID
});
// url looks like: https://id.endevre.com/ssologin?clientID=...&redirectURIs=https://app.example.com/start,https://app.example.com/middle,https://app.example.com/after-loginpairingID is optional. If omitted, Endevre ID will generate one on the server side. finalredirecturi is ensured to be the last item in redirectURIs.
Usage Patterns
Recommended: High-Level SSO Flow (Multi-Domain)
The simplest way to handle multi-domain SSO with automatic cookie management and redirects:
import endevreID from '@endevre/id-client';
const client = endevreID.createClient('<clientID>', '<secret>');
// === LOGIN FLOW ===
// 1. Generate login URL and redirect user to SSO
const loginUrl = client.createAuthUrl({
finalredirecturi: 'https://app.example.com/after-login',
redirecturis: ['https://app.example.com/callback']
});
window.location.href = loginUrl;
// 2. On callback page (after SSO redirects back with ?code=xxx&pairingID=yyy)
// This handles EVERYTHING: exchange, set cookies, redirect to continue SSO loop
const result = await client.handleAuthCallback();
// User is automatically redirected to continue multi-domain SSO loop
// === LOGOUT FLOW ===
// 1. Generate logout URL and redirect user to SSO
const logoutUrl = client.createLogoutUrl({
finalredirecturi: 'https://app.example.com/after-logout',
redirecturis: ['https://app.example.com/logout-callback']
});
window.location.href = logoutUrl;
// 2. On logout callback page (after SSO redirects back with ?pairingID=yyy)
// This handles EVERYTHING: remove cookies, redirect to continue SSO logout loop
client.handleLogoutCallback();
// User is automatically redirected to continue multi-domain SSO logout loopManual Control: Layered API
For custom flows, use the layered API with three levels of control:
// === LEVEL 1: Low-level (no redirects) ===
// Exchange code manually
const result = await client.exchange('authorization-code');
// Just set cookies, no redirect
client.setCookies(result);
// Later: just remove cookies, no redirect
client.removeCookies();
// === LEVEL 2: Mid-level (with redirects) ===
// Exchange and set cookies with redirect
const result = await client.exchange();
const pairingID = client.getPairingIDFromUrl();
client.setClientCookies(result, pairingID); // redirects to continue SSO loop
// Remove cookies with redirect
const pairingID = client.getPairingIDFromUrl();
client.removeClientCookies(pairingID); // redirects to continue SSO logout loop
// === LEVEL 3: High-level (complete orchestration) ===
await client.handleAuthCallback(); // does exchange + setCookies + redirect
client.handleLogoutCallback(); // does removeCookies + redirectChecking Callback Type
Use utility methods to determine what type of callback you're handling:
if (client.isAuthCallback()) {
// URL has ?code=xxx, handle login callback
await client.handleAuthCallback();
} else if (client.isLogoutCallback()) {
// URL has ?pairingID=xxx (no code), handle logout callback
client.handleLogoutCallback();
}Working with User Info
// Fetch user info for current token
const user = await client.getUserInfo();
console.log(user.firstName, user.email);
// Refresh token when needed
const refreshed = await client.refreshToken();
// Check if cookies are set
if (client.areCookiesSet()) {
console.log('User is authenticated');
}Exchange overloads and options
// Read code from URL, use default options
await client.exchange();
// Read from URL with custom options
await client.exchange({
names: { token: 'id_token', refresh: 'id_refresh', scopes: 'id_scopes', expire: 'id_expire' },
shouldRedirect: false
});
// Pass code directly
await client.exchange(code);
// Pass code with options
await client.exchange(code, { shouldRedirect: false });Notes:
exchange()reads thecodeparameter from the URL when not provided directlypairingIDis only needed forsetClientCookies()when redirecting- Outside the browser you must pass the authorization code explicitly (e.g.
exchange('code')) and include anoriginoption (or configuredefaultOrigin) so the Origin header is sent
Layered Cookie Management
Three levels of cookie control:
// LOW-LEVEL: Just set/remove cookies (no redirect)
client.setCookies(result, { isWildcardDomain: true });
client.removeCookies({ isWildcardDomain: true });
// MID-LEVEL: Set/remove cookies with optional redirect
client.setClientCookies(result, pairingID); // redirects by default
client.setClientCookies(result, undefined, { shouldRedirect: false }); // no redirect
client.removeClientCookies(pairingID); // redirects by default
client.removeClientCookies(undefined, { shouldRedirect: false }); // no redirect
// HIGH-LEVEL: Complete callback handling
await client.handleAuthCallback(); // extracts params, exchanges, sets cookies, redirects
client.handleLogoutCallback(); // extracts params, removes cookies, redirectsAPI Reference
Default export
import endevreID from '@endevre/id-client';
const client = endevreID.createClient(clientID, secret, options?);
const browserClient = endevreID.createBrowserClient(clientID, options?);createClient(clientID: string, secret: string, options?: EndevreClientOptions)
- clientID/secret: Provided by Endevre ID, used for server calls.
- options.cookiePrefix: Prefix for cookie names. Default
endevre_id_. - options.cookieDomain: Cookie domain.
- options.cookieSecure: Force
Securecookie attribute. Defaults based onhttps:. - options.cookieSameSite:
Lax(default) |Strict|None. - options.defaultOrigin: Fallback value used for the
Originheader when the browser is unavailable. - options.defaultReturnURL: Fallback URL used by
logout()when a browser origin isn't available. - options.onRedirect: Handler invoked when a redirect is requested but no browser environment is present.
createBrowserClient(clientID: string, options?: EndevreClientOptions)
- Returns a browser-oriented client that skips server-only features (no secret required).
- Provides
createAuthUrl,setClientCookies, andlogouthelpers. - Server-side calls such as
exchange()orrefreshToken()require a full client created withcreateClient.
client.createAuthUrl(params: CreateAuthUrlParams): string
- Builds
https://id.endevre.com/ssologinwithclientIDandredirectURIs. - Ensures
finalredirecturiis the last entry inredirectURIs. pairingIDis optional; include when you want to control correlation.
client.exchange(...)
Overloads:
exchange()exchange(options)exchange(code)exchange(code, options)
Options shape:
type ExchangeOptions = {
names?: { token?: string; refresh?: string; scopes?: string; expire?: string };
shouldRedirect?: boolean; // default true
origin?: string; // overrides Origin header for API requests
}Behavior:
- Reads
codefrom the URL when not provided directly. - Returns exchange result without setting cookies (use
setClientCookies()for that).
client.setClientCookies(result, pairingID?, options?)
- Stores tokens from exchange result in cookies.
- If
shouldRedirectis true (default) andpairingIDis provided, redirects to exchange endpoint. - Uses cookie names from options or defaults.
client.getUserInfo(): Promise<Record<string, unknown>>
- Fetches user information for the current authenticated user.
- Automatically handles token verification and validation.
client.refreshToken(): Promise<{ token, refresh, expire }>
Overloads:
refreshToken()refreshToken(options)refreshToken(refresh)refreshToken(refresh, options)
Options shape:
type RefreshTokenOptions = {
refresh?: string;
names?: { token?: string; refresh?: string; scopes?: string; expire?: string };
origin?: string; // overrides Origin header for API requests
}- Uses stored refresh token when not provided directly.
- Updates cookies automatically with new tokens.
- Supply an
originoption (or configuredefaultOrigin) outside the browser so the Origin header is sent.
client.handleAuthCallback(options?): Promise
High-level login callback handler
- Extracts
codeandpairingIDfrom URL automatically - Exchanges code for tokens
- Sets cookies
- Redirects to continue multi-domain SSO loop
// After SSO redirects back with ?code=xxx&pairingID=yyy
const result = await client.handleAuthCallback();
// Cookies set, user redirected automaticallyOptions:
{
origin?: string;
jwt?: boolean;
names?: { token?, refresh?, scopes?, expire? };
shouldRedirect?: boolean; // default true
isWildcardDomain?: boolean;
onRedirect?: (url: string) => void;
}client.handleLogoutCallback(options?): void
High-level logout callback handler
- Extracts
pairingIDfrom URL automatically - Removes authentication cookies
- Redirects to continue multi-domain SSO logout loop
// After SSO redirects back with ?pairingID=yyy
client.handleLogoutCallback();
// Cookies removed, user redirected automaticallyOptions:
{
names?: { token?, refresh?, scopes?, expire? };
shouldRedirect?: boolean; // default true
isWildcardDomain?: boolean;
onRedirect?: (url: string) => void;
}client.createLogoutUrl(params: CreateLogoutUrlParams): string
- Builds SSO logout URL with
clientIDandredirectURIs - Ensures
finalredirecturiis the last entry inredirectURIs pairingIDis optional
const logoutUrl = client.createLogoutUrl({
finalredirecturi: 'https://app.example.com/after-logout',
redirecturis: ['https://app.example.com/logout-callback']
});client.setCookies(result, options?)
Low-level: Set cookies without redirect
client.setCookies(result, {
names: { token: 'custom_token' },
isWildcardDomain: true
});client.setClientCookies(result, pairingID?, options?)
Mid-level: Set cookies with optional redirect
// With redirect (requires pairingID)
client.setClientCookies(result, pairingID);
// Without redirect
client.setClientCookies(result, undefined, { shouldRedirect: false });client.removeCookies(options?)
Low-level: Remove cookies without redirect
client.removeCookies({ isWildcardDomain: true });client.removeClientCookies(pairingID?, options?)
Mid-level: Remove cookies with optional redirect
// With redirect (requires pairingID)
client.removeClientCookies(pairingID);
// Without redirect
client.removeClientCookies(undefined, { shouldRedirect: false });client.isAuthCallback(): boolean
Returns true if current URL has code parameter (login callback).
client.isLogoutCallback(): boolean
Returns true if current URL has pairingID but no code (logout callback).
client.getPairingIDFromUrl(): string | undefined
Extracts pairingID from current URL.
client.logout(params?): void
⚠️ Deprecated: Use createLogoutUrl() and handleLogoutCallback() for proper SSO logout.
Local-only logout (doesn't go through SSO):
client.logout({ returnURL: 'https://app.example.com' });Configuration (cookies and options)
type EndevreClientOptions = {
cookiePrefix?: string; // default 'endevre_id_'
cookieDomain?: string;
cookieSecure?: boolean; // default inferred from https
cookieSameSite?: 'Lax' | 'Strict' | 'None'; // default 'Lax'
defaultOrigin?: string; // used to populate Origin header outside the browser
defaultReturnURL?: string; // used when logout runs outside the browser
onRedirect?: (url: string) => void; // called when redirecting without browser APIs
}Cookie names can be customized per-operation via options. For example:
await client.exchange({ names: { token: 'id_token', refresh: 'id_refresh' } });
await client.refreshToken({ names: { token: 'id_token', refresh: 'id_refresh' } });Commands (Yarn)
# Install deps
yarn install
# Type-check
yarn typecheck
# Run unit tests
yarn test
# Build CJS+ESM with types
yarn buildBrowser-only
The library targets browser environments for URL/cookie access and token verification.
