Synthigy is the identity provider; your application is an OAuth client. Two shapes cover nearly every application:
| Your app | Client | Flow | Who calls /data |
|---|---|---|---|
| Browser app (SPA) | public | authorization code + PKCE, in the browser | the browser, with the user's token |
| Server with a browser front (BFF) | confidential | authorization code + PKCE, on the server | the server: as itself, or as the user with acting_as |
Both start with a registered client and a redirect URI. The flows and lifetimes behind them are in OAUTH.md.
A browser app:
synthigy iam add-client "My SPA" --id my-spa --type public \
--grant authorization_code --redirect http://localhost:5173/
A backend:
synthigy iam add-client "My BFF" --id my-bff --type confidential \
--grant client_credentials --grant authorization_code \
--redirect http://localhost:5174/auth/callback \
--api Synthigy --role "Dataset Explorer" --trusted
Add --local to go through the portal of the instance on this machine instead
of a connected server. Things that bite first-timers:
/auth/callback is not /auth/callback/, http is not https.
A loopback URI (localhost, 127.0.0.1) may use any port. Register one
--redirect per URI, production included; the console edits them later.--api Synthigy is what makes the backend's own tokens usable. /data
checks the token's audience; a client with roles but no API gets tokens
that /data refuses.client_credentials to call /data as
itself, authorization_code to sign users in.--trusted lets the backend run requests as its users (acting_as)
and skips the consent screen.--secret on the
existing client sets a new one.The SDK runs the whole flow in the browser; the client is public and holds no
secret. Tokens stay in memory. A reload restores the session silently, through
a hidden iframe that rides the engine's session cookie, so the iframe's page
has to call silentCallback() before anything else:
import { createBrowserLogin, createClient, silentCallback } from '@synthigy/sdk'
if (!silentCallback()) {
const login = createBrowserLogin({
endpoint: SYNTHIGY_URL,
clientId: 'my-spa',
redirectUri: `${location.origin}/`, // registered with --redirect
})
const synthigy = createClient({ endpoint: SYNTHIGY_URL, tokenProvider: login.tokenProvider() })
if (login.isCallback()) {
const { returnTo } = await login.complete() // back from the login page
history.replaceState(null, '', returnTo)
} else {
await login.renew().catch(() => login.start()) // restore, else sign in
}
render(login.user) // id_token claims: sub, xid, …
}
Every call then carries the user's token, and /data applies the user's roles
and row rules (ACCESS.md). Renewal is automatic. The origin of
the redirect URI is allowed to call the API from the browser
(CORS.md).
The SDK owns the protocol; sessions, cookies and routes stay yours. Between
the start of a login and the callback, the flow has to remember one thing per
state (the PKCE verifier and nonce); you give the SDK a store for it, backed
by whatever holds your sessions. The in-memory store is for one process only.
const synthigy = createClient({
endpoint: SYNTHIGY_URL, clientId: CLIENT_ID, clientSecret: CLIENT_SECRET,
loginStore: new MemoryLoginStore(), // dev only
})
// GET /login
const { url } = await synthigy.login.start({
redirectUri: `${BASE_URL}/auth/callback`,
returnTo: req.query.returnTo ?? '/',
})
res.writeHead(302, { Location: url })
// GET /auth/callback?code=…&state=… (or ?error=… when the user cancelled)
if (error) {
const pending = await synthigy.login.cancel(state)
return redirect(pending?.returnTo ?? '/')
}
const { user, returnTo } = await synthigy.login.complete({
code, state, redirectUri: `${BASE_URL}/auth/callback`,
})
const sid = mySessionStore.create({ user }) // your session
setCookie(res, sid)
redirect(returnTo)
// Every call afterwards runs as the user
await synthigy.search('movie', args, selection, { actingAs: session.user.xid })
Python, Go, Clojure and PHP have the same login.start / login.complete
pair; each SDK's README shows it in its language (SDKS.md).
user.xid comes out of the id_token, so no lookup is needed. Passing it as
acting_as makes the user's own roles and row rules apply, not the backend's
(OAUTH.md). Never let the browser choose that
value: the backend resolves the user from its own session. A call without
acting_as runs as the backend's service user.
When the browser reaches Synthigy at a different URL than your server does
(containers, proxies), pass that URL as publicEndpoint to login.start.
Your app owns its session. Synthigy's own session, the one the login page and
silent renewal ride on, ends after 24 hours without activity and after 30 days
in any case; GET /oauth/logout ends it now. Access tokens last 15 minutes
and refresh tokens 24 hours, both changeable per client (OAUTH.md).
sub is the username and xid the user's record id. Profile claims (name,
email) come from GET /oauth/userinfo, not from the id_token; the browser
login merges them in with loadUserInfo: true.
Which sign-in methods a user sees — password, Google, a corporate directory — is the login page's business, configured in the console, not your app's (OAUTH.md, IAM_CONNECTORS.md).
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |