@selorax/signin
v1.0.1
Published
Sign in with SeloraX — OIDC client SDK for Node.js and browser
Downloads
100
Readme
Sign in with SeloraX
Official SDK for integrating SeloraX identity into your application. OAuth 2.0 Authorization Code flow with PKCE — for Node.js and the browser.
Overview
signin-with-selorax lets users authenticate with their SeloraX merchant or customer account — similar to "Sign in with Google". The SDK handles the full OpenID Connect flow: authorization URL generation, PKCE challenges, token exchange, user info retrieval, token refresh, and revocation.
Two modules, one package:
| Module | Environment | Import |
|--------|-------------|--------|
| SeloraxOIDC | Node.js (Express, Fastify, etc.) | require('signin-with-selorax') |
| SeloraxSignIn | Browser (React, Next.js, Vue, vanilla) | require('signin-with-selorax/frontend/selorax-signin') |
Installation
npm install signin-with-seloraxHow It Works
┌──────────┐ 1. Redirect ┌──────────────┐
│ │ ──────────────────> │ │
│ Your │ │ SeloraX │
│ App │ 2. Code │ Identity │
│ │ <────────────────── │ Provider │
└──────────┘ └──────────────┘
│
│ 3. Exchange code for tokens
│ 4. Fetch user info
│
v
┌──────────┐
│ Tokens │ access_token (1hr)
│ + User │ refresh_token (30d)
│ Info │ sub: "merchant:7"
└──────────┘Node.js — Quick Start
const express = require('express');
const session = require('express-session');
const { SeloraxOIDC, createCallbackHandler, requireAuth } = require('signin-with-selorax');
const app = express();
app.use(session({ secret: 'your-secret', resave: false, saveUninitialized: false }));
const oidc = new SeloraxOIDC({
issuer: 'https://api.selorax.io',
clientId: 'sx_oc_...',
clientSecret: 'sx_os_...',
redirectUri: 'http://localhost:3000/auth/callback',
});
// Redirect to SeloraX
app.get('/auth/login', (req, res) => {
const { url, state, pkce } = oidc.getAuthorizationUrl();
req.session.oauthState = state;
req.session.oauthPkce = pkce?.verifier;
res.redirect(url);
});
// Handle callback
app.get('/auth/callback', createCallbackHandler(oidc, {
serverSideUserInfo: true,
onSuccess: (req, res, { tokens, user }) => {
req.session.user = user;
req.session.tokens = {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
expires_at: Date.now() + (tokens.expires_in * 1000),
};
res.redirect('/dashboard');
},
onError: (req, res, error) => {
res.redirect('/auth/login?error=' + encodeURIComponent(error.error));
},
}));
// Protected route
app.get('/dashboard', requireAuth({ loginUrl: '/auth/login' }), (req, res) => {
res.json({ user: req.seloraxUser });
});
// Logout
app.get('/auth/logout', async (req, res) => {
if (req.session.tokens?.access_token) {
await oidc.revokeToken(req.session.tokens.access_token).catch(() => {});
}
req.session.destroy(() => res.redirect('/'));
});
app.listen(3000);Browser — Quick Start
import { SeloraxSignIn } from 'signin-with-selorax/frontend/selorax-signin';
const signIn = new SeloraxSignIn({
issuer: 'https://api.selorax.io',
clientId: 'sx_oc_...',
redirectUri: window.location.origin + '/auth/callback',
});Login page:
document.getElementById('login-btn').addEventListener('click', () => {
signIn.login();
});Callback page:
const result = signIn.parseCallback();
if (result.error) {
console.error(result.error, result.description);
} else {
// Send code + verifier to your backend for token exchange
const res = await fetch('/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: result.code, verifier: result.verifier }),
});
}API Reference
SeloraxOIDC — Server-Side Client
Constructor
new SeloraxOIDC(config)Falls back to environment variables when config values are omitted:
SELORAX_OIDC_ISSUER=https://api.selorax.io
SELORAX_OIDC_CLIENT_ID=sx_oc_...
SELORAX_OIDC_CLIENT_SECRET=sx_os_...
SELORAX_OIDC_REDIRECT_URI=http://localhost:3000/auth/callbackMethods
getAuthorizationUrl(options?) — Generate the login redirect URL.
const { url, state, pkce } = oidc.getAuthorizationUrl({
scopes: ['openid', 'profile', 'email', 'store'], // optional override
storeId: 22, // optional: request access for a specific store
nonce: '...', // optional: OIDC nonce
usePKCE: true, // default: true (always true for public clients)
});
// url → full authorization URL to redirect to
// state → CSRF token to validate on callback
// pkce → { verifier, challenge, method } or nullexchangeCode(code, codeVerifier?) — Exchange authorization code for tokens.
const tokens = await oidc.exchangeCode(code, pkce.verifier);
// {
// access_token: "sx_it_...",
// refresh_token: "sx_ir_...",
// token_type: "Bearer",
// expires_in: 3600,
// scope: "openid profile email"
// }getUserInfo(accessToken) — Fetch user info with Bearer token (GET request).
const user = await oidc.getUserInfo(tokens.access_token);getUserInfoServer(accessToken) — Fetch user info with client credentials (POST request). Validates that the token was issued to your client. Requires clientSecret.
const user = await oidc.getUserInfoServer(tokens.access_token);
// {
// sub: "merchant:7",
// name: "John Doe",
// picture: "https://...",
// email: "[email protected]",
// email_verified: true,
// store_id: 22,
// store_name: "My Store",
// role: "admin"
// }refreshToken(refreshToken) — Get a new access token.
const newTokens = await oidc.refreshToken(tokens.refresh_token);revokeToken(token, tokenTypeHint?) — Revoke an access or refresh token.
await oidc.revokeToken(tokens.access_token, 'access_token');
await oidc.revokeToken(tokens.refresh_token, 'refresh_token');getDiscovery() — Fetch the OpenID Connect discovery document.
const config = await oidc.getDiscovery();
// config.authorization_endpoint, config.scopes_supported, etc.Static Helpers
SeloraxOIDC.parseSubject(sub) — Extract user type and ID from the sub claim.
const { userType, userId } = SeloraxOIDC.parseSubject('merchant:7');
// userType: 'merchant', userId: 7SeloraxOIDC.validateState(received, expected) — Timing-safe state comparison.
if (!SeloraxOIDC.validateState(req.query.state, req.session.oauthState)) {
return res.status(400).send('Invalid state');
}SeloraxOIDC.checkCallbackError(query) — Check if the callback URL contains an OAuth error.
const error = SeloraxOIDC.checkCallbackError(req.query);
if (error) console.error(error.error, error.description);SeloraxSignIn — Browser Client
Constructor
new SeloraxSignIn(config)Methods
login(options?) — Redirect the user to SeloraX authorization. Generates state + PKCE automatically and stores them in sessionStorage.
await signIn.login();
await signIn.login({ scopes: ['openid', 'store'], storeId: 22 });parseCallback(url?) — Parse the callback URL after redirect. Validates state and returns the authorization code + PKCE verifier.
const result = signIn.parseCallback();
// Success: { code: "sx_ic_...", state: "...", verifier: "..." }
// Error: { error: "access_denied", description: "..." }getVerifier() / getState() — Access stored values for manual flows.
Express Middleware
createCallbackHandler(oidc, options)
Creates an Express route handler that processes the OAuth callback automatically: validates state, exchanges the code, optionally fetches user info, and calls your callback.
app.get('/auth/callback', createCallbackHandler(oidc, {
onSuccess: (req, res, { tokens, user }) => { ... },
onError: (req, res, error) => { ... },
fetchUser: true, // default: true
serverSideUserInfo: false, // default: false
}));Expects req.session.oauthState and req.session.oauthPkce to be set by the login route.
requireAuth(options?)
Middleware that protects routes by checking for an authenticated session.
// Return 401 if not authenticated
app.get('/api/me', requireAuth(), handler);
// Redirect to login page
app.get('/dashboard', requireAuth({ loginUrl: '/auth/login' }), handler);
// Restrict to merchants only
app.get('/admin', requireAuth({ userType: 'merchant' }), handler);
// Custom user extraction
app.get('/api/data', requireAuth({ getUser: (req) => req.customSession?.user }), handler);Sets req.seloraxUser on authenticated requests.
Scopes
SeloraX supports two categories of scopes: identity scopes (control which user claims are returned) and resource scopes (control access to store data via the API).
Identity Scopes
Resource Scopes
Resource scopes follow the read:resource / write:resource pattern. Requesting write:X implies read:X — you don't need to request both.
Requesting Resource Scopes
Pass resource scopes alongside identity scopes when generating the authorization URL:
const { url, state, pkce } = oidc.getAuthorizationUrl({
scopes: ['openid', 'profile', 'email', 'read:orders', 'write:products'],
});The store owner will see a consent screen listing the requested permissions. If the app later requests additional scopes, the user is re-prompted for consent only for the new scopes.
Consent Behavior
- First authorization: User sees all requested scopes and must approve
- Returning authorization: If all scopes were previously granted, consent is skipped
- Scope changes: If the app requests new scopes not previously granted, the consent screen re-appears with "NEW" badges on the additional permissions
- Token refresh: Refreshed tokens receive the intersection of the originally granted scopes and the client's current
allowed_scopes— if a scope is removed from the client, refreshed tokens no longer include it
User Types
The sub claim identifies the user type and ID:
| Format | Description |
|--------|-------------|
| merchant:7 | Store administrator |
| customer:42 | Store customer |
const { userType, userId } = SeloraxOIDC.parseSubject(user.sub);Token Lifetimes
| Token | Lifetime | Prefix |
|-------|----------|--------|
| Authorization code | 60 seconds | sx_ic_ |
| Access token | 1 hour | sx_it_ |
| Refresh token | 30 days | sx_ir_ |
Client Types
Error Handling
The SDK throws typed errors for different failure scenarios:
const { SeloraxOIDCError, SeloraxTokenError, SeloraxAuthorizationError } = require('signin-with-selorax');
try {
const tokens = await oidc.exchangeCode(code, verifier);
} catch (err) {
if (err instanceof SeloraxTokenError) {
// Token exchange or refresh failed
console.error(err.code); // e.g. 'invalid_grant'
console.error(err.response); // raw server response
} else if (err instanceof SeloraxOIDCError) {
// Network error, timeout, or config issue
console.error(err.code); // e.g. 'timeout', 'network_error'
}
}| Error Class | Codes | When |
|-------------|-------|------|
| SeloraxOIDCError | missing_config, timeout, network_error, discovery_failed | Configuration or network issues |
| SeloraxTokenError | token_exchange_failed, token_refresh_failed, userinfo_failed, invalid_grant | Token endpoint failures |
| SeloraxAuthorizationError | authorization_error | Authorization flow errors |
PKCE Utilities
For advanced use cases, PKCE functions are exported directly:
const { generatePKCE, generateCodeVerifier, generateCodeChallenge } = require('signin-with-selorax');
const { verifier, challenge, method } = generatePKCE();
// method is always 'S256'
// Or individually
const verifier = generateCodeVerifier(64);
const challenge = generateCodeChallenge(verifier);Constants
const {
SCOPES, TOKEN_PREFIXES, ENDPOINTS,
RESOURCE_SCOPES, SCOPE_GROUPS, ALL_VALID_SCOPES,
} = require('signin-with-selorax');
// Identity scope names
SCOPES.OPENID // 'openid'
SCOPES.STORE // 'store'
// Token prefixes
TOKEN_PREFIXES.ACCESS_TOKEN // 'sx_it_'
TOKEN_PREFIXES.CLIENT_ID // 'sx_oc_'
// API endpoints
ENDPOINTS.AUTHORIZE // '/api/oauth/authorize'
ENDPOINTS.TOKEN // '/api/oauth/token'
ENDPOINTS.USERINFO // '/api/oauth/userinfo'
// Resource scope metadata (keyed by scope string)
RESOURCE_SCOPES['read:orders']
// { resource: 'orders', action: 'read', label: 'Read Orders', description: 'View orders and order details' }
// Scope groups (keyed by resource name, useful for building scope selection UIs)
SCOPE_GROUPS.orders
// { label: 'Orders', description: 'Access to store orders', read: 'read:orders', write: 'write:orders', icon: 'Package' }
// All 27 valid scopes (5 identity + 22 resource)
ALL_VALID_SCOPES // ['openid', 'profile', 'email', 'phone', 'store', 'read:orders', ...]Discovery
The OpenID Connect discovery document is available at:
GET {issuer}/.well-known/openid-configurationconst discovery = await oidc.getDiscovery();Returns all supported endpoints, scopes, grant types, and claim types as defined by the OpenID Connect Discovery spec.
Project Structure
signin-with-selorax/
index.js Main entry point
lib/
selorax-oidc.js Core OIDC client (Node.js)
express.js Express callback handler + auth middleware
pkce.js PKCE S256 utilities
constants.js Scopes, endpoints, token prefixes
errors.js Error classes
frontend/
selorax-signin.js Browser client (PKCE via Web Crypto API)Zero runtime dependencies. Uses Node.js built-in crypto module and native fetch (Node 18+). The browser module uses the Web Crypto API.
