Announcing CIMD support for MCP Client registration
Learn more
B2B Authentication
Mar 11, 2026

Introducing multi-app authentication

No items found.

Most B2B products are not a single app. There's a web dashboard, a documentation portal, maybe a companion mobile app, a CLI tool, or an analytics SPA embedded inside the main product. Each surface exists for a good reason, but authentication has always lagged behind.

Connecting these surfaces through a shared session has meant one of two things: building your own cross-app session broker, or stitching together multiple auth vendors and syncing user state between them. Both approaches work, eventually. Neither is something you want to maintain.

Multi-App Authentication is now part of Scalekit Full Stack Auth. It extends the auth infrastructure you already have, giving every app in your product its own OAuth client, its own credentials, and its own token lifecycle, while sharing a single underlying user session. Login once on any surface; every other app recognises you.

How it works

The model is simple. Your Scalekit environment is the identity authority. Each application — web app, SPA, mobile app, CLI, desktop client — registers as an independent OAuth client within that environment. Each gets its own client_id. Web apps additionally get a client_secret for server-side flows.

When a user authenticates on any surface, Scalekit creates a session tied to the environment, not to any individual app. Any other registered application can exchange an authorization code for tokens against the same session without prompting the user to log in again. The session is shared. The tokens are scoped per client.

1. Separate credentials per appEach app has its own client_id. Web apps rotate client_secret independently. A compromised SPA client cannot call server-side Management APIs.

2. Shared session layerThe user authenticates once. Scalekit's session persists at the environment level. Subsequent auth requests from other registered apps skip the login prompt.

3. Correct OAuth flows per client typeWeb apps use Authorization Code + client_secret. SPAs and native apps enforce PKCE automatically. Public clients cannot generate secrets. The dashboard enforces this at the application type level — not a config toggle.

4. Per-app token configurationAccess token expiry can be overridden per application. A short-lived token for an analytics SPA and a longer-lived token for a backend CLI coexist in the same environment without touching each other.

What you can build

SaaS dashboard + documentation portal

Your main app lives at app.yourproduct.com. Your docs portal lives at docs.yourproduct.com — often a separate deployment, sometimes a different framework entirely.

Without a shared session layer, the docs portal either has no auth (so you can't gate content or personalise by plan), or you hand-roll cookie sharing between domains. With Multi-App Authentication, both apps register as web applications under the same environment. If the user already has an active session from the dashboard, they land on the docs page they were trying to reach — no login screen.

DashboardDocs portalApplication typeWeb AppWeb AppManagement API accessEnabledDisabledBackchannel logoutConfiguredConfigured

Web app + SPA

You have a server-side web application. Alongside it lives a SPA — an analytics dashboard, a reporting widget, a customer-facing portal that runs entirely in the browser.

SPAs cannot store secrets. Multi-App Authentication handles this by registering the SPA as a separate client type, enforcing PKCE on every auth request, and issuing no client secret. The web app and the SPA share the same user session. If the user is logged into the web app, the SPA's auth request returns an authorization code without a login prompt.

// SPA: generate PKCE pair and redirect to authorize const verifier = generateCodeVerifier(); const challenge = await generateCodeChallenge(verifier); const url = new URL(`${ENV_URL}/oauth/authorize`); url.searchParams.set("response_type", "code"); url.searchParams.set("client_id", SPA_CLIENT_ID); url.searchParams.set("redirect_uri", CALLBACK_URL); url.searchParams.set("scope", "openid profile email offline_access"); url.searchParams.set("code_challenge", challenge); url.searchParams.set("code_challenge_method", "S256"); window.location.href = url.toString();

On callback, the SPA exchanges the code ,no client_secret, only the verifier stored in memory:

const params = new URLSearchParams({ grant_type: "authorization_code", client_id: SPA_CLIENT_ID, code: authorizationCode, redirect_uri: CALLBACK_URL, code_verifier: storedVerifier, }); const res = await fetch(`${ENV_URL}/oauth/token`, { method: "POST", body: params }); const { access_token, refresh_token } = await res.json();

Web app + native mobile app

Your product ships a mobile companion app. The mobile app accesses the same backend API as the web app but lives in a completely different runtime — no cookies, no shared browser session, custom URI scheme callbacks.

Mobile and desktop apps register as native application types. PKCE is required, no client_secret is issued. The login flow opens the system browser, not an embedded WebView — per RFC 8252, this is the correct approach. The session sharing mechanism works across the browser boundary: if a user is already logged into your web app in Safari and opens your iOS app, they may not need to re-enter credentials.

Tokens are returned via a custom URI scheme (myapp://callback) or a universal link, then stored in the platform's secure store.

Platform
Storage
iOS
Keychain Services
Android
EncryptedSharedPreferences / Keystore
macOS
Keychain
Windows
Credential Manager / DPAPI
Linux
Secret Service API (libsecret)

How we run it: app.scalekit.com and docs.scalekit.com

The proof is in the pudding. Ee didn't just build Multi-App Authentication for our customers, we run it ourselves.

app.scalekit.com and docs.scalekit.com run against the same Scalekit production environment. Each is registered as a separate web application with its own client_id and client_secret. The dashboard app has Management API access enabled. it needs to manage users, organizations, and credentials on behalf of authenticated users. The docs app does not. It only needs to know who the user is.

When a developer authenticates on app.scalekit.com, Scalekit creates an environment-level session. If they then navigate to docs.scalekit.com, the docs portal's auth redirect returns an authorization code immediately. The login page does not appear. Developers who are actively working in the dashboard don't hit a login wall when they switch tabs to the docs — this is the experience we wanted, and it's the one we built Multi-App Auth to enable.

Both apps configure backchannel logout URIs. When a user logs out on either surface, Scalekit sends a signed notification to both endpoints. Each backend clears its own session. Logging out of the dashboard terminates the docs session. Logging out of the docs terminates the dashboard session. A user who explicitly logs out is logged out everywhere.

Turns out, our customers wanted the same thing, and found their own way to the same pattern.

"We host our docs on GitBook and wanted to gate them for logged-in users without building a separate auth layer. We registered GitBook as a new application in Scalekit, dropped in the OIDC credentials, and that was it — our users now single sign-on seamlessly between the product and the docs. No separate login wall, no session mismatch. Took maybe twenty minutes to set up." — Head of Engineering, at a AI-powered Revenue Autopilot

Building this on our own infrastructure reinforced a few things that made it into the product:

Per-app token expiry is essential. The docs portal has different session semantics than the dashboard. A single environment-wide expiry would force a choice between over-privileged docs sessions or unnecessary friction on the dashboard.

PKCE must be enforced at the application type level, not as a toggle. Making it non-editable for SPA and native client types removes a class of misconfiguration entirely.

Backchannel logout needs to be verified, not trusted. Scalekit sends a signed JWT to your backchannel endpoint. Validate the signature against JWKS before invalidating the session.

Getting started

If you're already using Scalekit Full Stack Auth, adding a new application takes under a minute.

  1. Go to app.scalekit.com → Developers → Applications
  2. Click Create Application, choose a name and type
  3. Copy the generated client_id (and client_secret for web apps — shown only once)
  4. Add redirect URLs and implement using the guide for your app type

Questions? Reach us at support@scalekit.com or talk to an engineer at scalekit.com.

On this page
Share this article

Acquire enterprise customers with zero upfront cost

Every feature unlocked. No hidden fees.
Start Free
$0
/ month
1 million Monthly Active Users
100 Monthly Active Organizations
1 SSO connection
1 SCIM connection
10K Connected Accounts
Unlimited Dev & Prod environments

{
 "@context": "https://schema.org",
 "@type": "FAQPage",
 "mainEntity": [
   {
     "@type": "Question",
     "name": "What is Multi-App Authentication?",
     "acceptedAnswer": {
       "@type": "Answer",
       "text": "Multi-App Authentication is part of Scalekit Full Stack Auth. It gives every app in your product its own OAuth client, credentials, and token lifecycle, while sharing a single underlying user session. Login once on any surface and every other app recognises you."
     }
   },
   {
     "@type": "Question",
     "name": "How does the shared session work across multiple apps?",
     "acceptedAnswer": {
       "@type": "Answer",
       "text": "When a user authenticates on any surface, Scalekit creates a session tied to the environment, not to any individual app. Any other registered application can exchange an authorization code for tokens against the same session without prompting the user to log in again."
     }
   },
   {
     "@type": "Question",
     "name": "What app types does Multi-App Authentication support?",
     "acceptedAnswer": {
       "@type": "Answer",
       "text": "Multi-App Authentication supports web applications, single page applications (SPAs), native mobile apps, and desktop clients. Each registers as an independent OAuth client with the correct flow enforced per type — Authorization Code for web apps, PKCE for SPAs and native apps."
     }
   },
   {
     "@type": "Question",
     "name": "Can SPAs use Multi-App Authentication without a client secret?",
     "acceptedAnswer": {
       "@type": "Answer",
       "text": "Yes. SPAs register as a separate client type and PKCE is enforced on every auth request. No client secret is issued. The SPA shares the same user session as the web app without ever handling a secret."
     }
   },
   {
     "@type": "Question",
     "name": "What happens when a user logs out?",
     "acceptedAnswer": {
       "@type": "Answer",
       "text": "Both apps configure backchannel logout URIs. When a user logs out on either surface, Scalekit sends a signed JWT notification to both endpoints. Each backend clears its own session. Logging out of one app terminates the session across all registered apps."
     }
   },
   {
     "@type": "Question",
     "name": "Does Scalekit use Multi-App Authentication itself?",
     "acceptedAnswer": {
       "@type": "Answer",
       "text": "Yes. app.scalekit.com and docs.scalekit.com run against the same Scalekit production environment, each registered as a separate web application. Authenticating on the dashboard means no login wall when navigating to the