Single Sign-On
Every Inklura app — the console, SMS Designer, SEO, Mail, POS, support — shares one login. SSO works through two cooperating mechanisms:
- OIDC PKCE — each app authenticates users against the shared identity provider, and the IdP's own session makes subsequent logins silent.
- A console→app hand-off token — for silently launching an already-authenticated console user into a sibling app (used when embedding).
For the broader authentication picture (session cookies, Bearer JWTs, magic links) see Authentication.
Mechanism 1 — OIDC PKCE per app
The identity provider is auth.1clic.pro. Each *.inklura.fr app is its own public
PKCE (S256) OIDC client — a public client with no client secret:
- e.g. client id
smsdesigner-prism, redirecthttps://smsdesigner.inklura.fr/auth/callback - or the shared-realm client
inklura-admin, used by seo + pos
Scopes: openid profile email offline_access.
Host-scoped session cookies
After the OIDC round-trip, each app mints its own host-scoped session cookie:
Set-Cookie: <session>; Path=/; SameSite=Lax; Secure
Note there is no Domain= attribute — the cookie is scoped to that one host, not shared
across subdomains.
Why login stays silent across subdomains
Users appear "logged in everywhere," but not because one cookie is shared. Each app has
its own host-scoped cookie. What is shared is the IdP's own session at auth.1clic.pro:
when a user who has already authenticated visits a second app, that app's PKCE round-trip
completes silently at the IdP (no password re-entry), and the app mints its local cookie.
app B: no local cookie ──> redirect to auth.1clic.pro
IdP already has a session ──> silent authorize ──> /auth/callback
app B mints its OWN host-scoped cookie
node:crypto caveat. PRISM's V8 isolate stubs node:crypto and crypto.subtle.digest, so
SHA-256 / HMAC are hand-rolled in pure JS in each app's oidc.ts. By default the
id_token is decoded, not signature-verified — the trust anchor is the TLS-protected
token exchange with the IdP. Optional RS256-via-JWKS verification is available behind a flag.
Mechanism 2 — console→app hand-off token
To silently drop an already-authenticated console user into a sibling app (for example inside an iframe), the console mints a short-lived signed token and redirects the browser to the target app, which verifies it and mints its own local session.
Token format
b64url(JSON(payload)) + "." + b64url(hmacSha256(INKLURA_SSO_SECRET, b64payload))
- The HMAC is computed over the b64url-encoded payload using
INKLURA_SSO_SECRET. - TTL: 120 seconds.
Payload:
{ "sub": "…", "email": "…", "name": "…", "tenant_id": "…", "aud": "…", "iat": 0, "exp": 0 }
The aud claim is the target app's OIDC client id. Because the signature covers aud,
the token is not replayable at a different app — a token minted for app A fails app B's
audience check.
The mint route
GET /api/manage/designer-sso?app=<key>&next=<path>&embed=1
- Gated by the console's own access check.
- Carries only the caller's own identity (it can't impersonate another user).
- Resolves the sibling app on the same registrable domain and 302-redirects there with the token.
The landing route (app side)
The target app exposes a landing route that consumes the token:
| Landing route | Apps |
|---|---|
/auth/sso |
mail, sms, ads |
/auth/manage-sso |
seo, pos, support |
The landing route:
- Verifies the HMAC.
- Checks
exp(not expired). - Asserts
aud === CLIENT_ID(this app is the intended audience). - Mints the local session and redirects to
next.
console (authenticated) ──GET /api/manage/designer-sso?app=mail&next=/inbox&embed=1
mint token (aud = mail's client id, TTL 120s)
302 ──> https://mail.inklura.fr/auth/sso?token=…&next=/inbox
verify HMAC + exp + aud === CLIENT_ID
mint local session ──> 302 /inbox
Same-site vs new-tab
When the console launches a sibling this way, same-site apps are embedded in an iframe; cross-site apps are launched in a new tab. The framing rules that make same-site embedding work are covered in Iframe Embedding.
See also
- Authentication — sessions, Bearer JWTs, OIDC, magic links
- Iframe Embedding — CSP and embedding a sibling app