Server Client API
Import:
import { createFrappeSSO } from 'sso-frappe/server';createFrappeSSO(config)
Creates a server-side Frappe SSO client. This is the only entry point that accepts clientSecret.
const sso = createFrappeSSO({
baseUrl: 'https://erp.example.com',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://app.example.com/api/auth/callback/frappe',
scope: ['openid'], // optional, default ['openid']
usePkce: true, // optional, default true
allowInsecureHttp: false, // optional, default false
timeoutMs: 10_000, // optional, default 10000
});Returns a FrappeSSOClient with the methods below.
createAuthorizationUrl()
Generates the authorization URL, state, and PKCE pair. Use this when your backend owns the redirect (e.g. Nuxt Nitro, Express).
const { url, state, codeVerifier } = await sso.createAuthorizationUrl();
// Store state + codeVerifier in a server-side session
req.session.frappeState = state;
req.session.frappeVerifier = codeVerifier;
// Redirect the user
res.redirect(url);exchangeCode({ code, codeVerifier })
Exchanges the authorization code for an access token. Server-only — requires clientSecret.
const token = await sso.exchangeCode({
code: 'auth-code-from-frappe',
codeVerifier: 'verifier-from-session',
});
// token.accessToken — string
// token.tokenType — string (usually 'Bearer')
// token.expiresIn — number | undefined
// token.refreshToken — string | undefined
// token.raw — full response from FrappeThrows FrappeSSOTokenError on failure.
getUserProfile(token)
Fetches the user profile from Frappe's userinfo endpoint.
const profile = await sso.getUserProfile(token);
// profile.subject — stable user ID (the `sub` claim)
// profile.email — email address
// profile.name — full name
// profile.username — Frappe username
// profile.image — avatar URL
// profile.roles — string[] (e.g. ['System User', 'Employee'])
// profile.raw — full userinfo responseThrows FrappeSSOProfileError on failure.
discover()
Fetches the OIDC discovery document. Called automatically by exchangeCode and getUserProfile if explicit endpoints aren't configured.
const metadata = await sso.discover();
// metadata.authorization_endpoint
// metadata.token_endpoint
// metadata.userinfo_endpoint
// ...Throws FrappeSSODiscoveryError if the endpoint is unreachable or malformed.
validateState(stored, received)
Validates that the state returned by Frappe matches the one you stored. Exported from both sso-frappe and sso-frappe/server.
import { validateState } from 'sso-frappe/server';
validateState(storedState, receivedState); // throws FrappeSSOStateError on mismatchConfiguration reference
| Field | Required | Default | Description |
|---|---|---|---|
baseUrl |
Yes | — | Frappe instance URL |
clientId |
Yes | — | OAuth client ID |
clientSecret |
Yes | — | OAuth client secret (server-only) |
redirectUri |
Yes | — | Consumer callback URL |
scope |
No | ['openid'] |
OAuth scopes |
usePkce |
No | true |
PKCE S256 |
allowInsecureHttp |
No | false |
Allow http:// for local dev |
timeoutMs |
No | 10000 |
HTTP timeout |
authorizationEndpoint |
No | discovery | Explicit override |
tokenEndpoint |
No | discovery | Explicit override |
userInfoEndpoint |
No | discovery | Explicit override |
discoveryEndpoint |
No | ${baseUrl}/.well-known/openid-configuration |
Explicit override |
Errors
All errors extend FrappeSSOError. Error messages never include secrets.
| Error | When |
|---|---|
FrappeSSOConfigError |
Invalid config (missing field, bad URL, etc.) |
FrappeSSOStateError |
State mismatch or missing state/code/verifier |
FrappeSSOTokenError |
Token exchange failed |
FrappeSSOProfileError |
Userinfo fetch failed |
FrappeSSODiscoveryError |
Discovery endpoint unreachable/malformed |