Server SDKs and Adapters
Server SDKs
Section titled “Server SDKs”Seamless Auth currently ships two official server-side packages:
@seamless-auth/core@seamless-auth/express
These packages help your backend become the browser-facing security boundary between your app and the private auth server.
Which One Should You Use?
Section titled “Which One Should You Use?”| Package | Use it when | What it gives you |
|---|---|---|
@seamless-auth/express | Your backend is Express | Official adapter, mounted auth routes, middleware, role guards |
@seamless-auth/core | You need a custom adapter or framework integration | Framework-agnostic auth, cookie, OAuth, and role primitives |
If you already run Express, start with @seamless-auth/express.
What The Backend Layer Does
Section titled “What The Backend Layer Does”Your backend integration layer is responsible for:
- receiving browser requests
- owning signed, HTTP-only auth cookies
- validating authenticated state
- enforcing authorization
- proxying auth flows to the private auth API
- talking to the auth server with service credentials
That architecture is intentional. Browser cookies should not be forwarded directly to
seamless-auth-api as if the auth server were your public application API.
@seamless-auth/express
Section titled “@seamless-auth/express”The Express package is the supported batteries-included server integration.
It provides:
createSeamlessAuthServer(...)requireAuth({ cookieSecret })requireRole(roleOrRoles)getSeamlessUser(...)createEnsureCookiesMiddleware(...)- scoped role helpers re-exported from
@seamless-auth/core
Typical shape
Section titled “Typical shape”import express from 'express'import cookieParser from 'cookie-parser'import createSeamlessAuthServer, { requireAuth, requireRole, type SeamlessAuthServerOptions,} from '@seamless-auth/express'
const app = express()app.use(express.json())app.use(cookieParser())
const authOptions: SeamlessAuthServerOptions = { authServerUrl: process.env.AUTH_SERVER_URL!, cookieSecret: process.env.COOKIE_SIGNING_KEY!, serviceSecret: process.env.API_SERVICE_TOKEN!, issuer: process.env.APP_ORIGIN!, audience: process.env.AUTH_SERVER_URL!, jwksKid: process.env.JWKS_KID,}
app.use('/auth', createSeamlessAuthServer(authOptions))
app.get('/api/me', requireAuth({ cookieSecret: authOptions.cookieSecret }), (req, res) => { res.json({ user: req.user })})
app.get( '/admin/users', requireAuth({ cookieSecret: authOptions.cookieSecret }), requireRole('admin:read'), (_req, res) => { res.json({ ok: true }) },)Required options
Section titled “Required options”| Option | Purpose |
|---|---|
authServerUrl | Base URL for the Seamless Auth instance |
cookieSecret | Secret used to verify and sign adapter cookies |
serviceSecret | Shared service secret for machine-to-machine calls to the auth instance |
issuer | Issuer/origin value for app-issued cookies |
audience | Expected audience, usually the auth server URL |
Optional values include jwksKid, cookieDomain, custom cookie names, and messaging.
Managed vs self-hosted configuration
Section titled “Managed vs self-hosted configuration”The options are the same on both paths. Only the values differ, and where the service secret comes from.
| Option | Managed | Self-hosted |
|---|---|---|
authServerUrl | Your Auth Server URL, for example https://<app-id>.seamlessauth.com | The auth API, for example http://localhost:5312 |
audience | The same Auth Server URL | The same auth server URL |
serviceSecret | A service token generated in the portal (shown once) | The auth API’s API_SERVICE_TOKEN from your local stack |
On the managed path, generate the service token from your application’s Auth API Configuration
card and read it from a server-side environment variable. The portal’s connection snippet reads it
from SEAMLESS_SERVICE_TOKEN. See the Managed Quickstart. The
serviceSecret is never sent raw: the adapter uses it to sign short-lived service tokens for calls
to the auth instance.
Mounted Adapter Routes
Section titled “Mounted Adapter Routes”When mounted at /auth, the Express adapter exposes auth-compatible browser routes such as:
/auth/login/auth/oauth/providers/auth/oauth/:providerId/start/auth/oauth/:providerId/callback/auth/webAuthn/login/start/auth/webAuthn/login/finish/auth/webAuthn/register/start/auth/webAuthn/register/finish/auth/otp/*/auth/magic-link*/auth/step-up/*/auth/organizations/*/auth/users/me/auth/users/credentialsDELETE /auth/logoutDELETE /auth/logout/all/auth/admin/*/auth/internal/*/auth/system-config/*
The adapter route spelling is webAuthn; the raw auth API route group is /webauthn.
Auth Middleware
Section titled “Auth Middleware”requireAuth({ cookieSecret }) verifies the signed access cookie and attaches the decoded payload
to req.user.
app.get('/api/profile', requireAuth({ cookieSecret }), (req, res) => { res.json({ user: req.user })})This middleware verifies an already-issued access cookie. Session refresh is handled by the adapter’s cookie middleware.
With authServerUrl and audience it also accepts the auth API’s own access token in
Authorization: Bearer, verified against the auth API’s JWKS (issuer, audience, expiry and
typ: "access"). This is how a native client authenticates; see
Bearer Transport For Native Clients. A cookie wins when
both are present, and the two options must be given together.
const guard = requireAuth({ cookieSecret, authServerUrl: process.env.AUTH_SERVER_URL, audience: process.env.AUTH_SERVER_URL,})On the bearer path req.user.email and req.user.phone are not set, because the access token
does not carry them. getSeamlessUser(req, options) loads the profile for either credential.
requireRole(required) performs authorization only. It expects requireAuth to have populated
req.user.
app.post( '/settings', requireAuth({ cookieSecret }), requireRole(['admin:write', 'settings:write']), updateSettings,)Scoped role compatibility:
| Granted role | Satisfies |
|---|---|
admin | admin, admin:read, admin:write |
admin:write | admin:write, admin:read |
admin:read | admin:read only |
Bearer Transport For Native Clients
Section titled “Bearer Transport For Native Clients”Browsers get the cookie contract: the adapter holds the auth API’s tokens in signed httpOnly
cookies and the page never sees them. A native app has no cookie jar, so the same routes serve a
second contract when a request carries the header x-seamless-auth-transport: bearer:
- The client presents the token a route needs in
Authorization: Bearer. Pre-auth routes (OTP, passkey login, magic link) take the ephemeral token/loginor/registration/registerreturned; signed-in routes take the access token. - Session-issuing responses come back whole,
tokenandrefreshTokenincluded, and no cookie is set. The adapter still verifies the auth API’s signature on the access token first. POST /auth/refreshrotates the session:Authorization: Bearer <refreshToken>in, a new pair out. The auth API treats a replayed refresh token as theft and revokes the chain (401 { "error": "refresh_token_reused" }), so a client refreshes once at a time.- Delivery mode, forwarded client IP and user agent, and the service token behave exactly as in cookie transport.
The header selects the transport rather than the presence of Authorization, because the first
request of a flow carries no token in either. Requests without the header are unchanged.
POST /auth/login -> { token: <ephemeral>, loginMethods, ... }POST /auth/otp/verify-login-email-otp -> { token: <access>, refreshToken, ... }GET /auth/users/me Authorization: Bearer <access>POST /auth/refresh Authorization: Bearer <refreshToken>@seamless-auth/react-native speaks this contract for you; the
React Native SDK page covers the client side. Your own routes accept
the same access token through requireAuth with authServerUrl and audience, above.
Messaging Delivery
Section titled “Messaging Delivery”The Express adapter can own email and SMS delivery for auth-message flows.
createSeamlessAuthServer({ authServerUrl, cookieSecret, serviceSecret, issuer, audience, messaging: { email: emailTransport, sms: smsTransport, handlers: { magicLinkEmail: async (message) => { await deliver(message) }, }, },})When messaging is configured, the adapter requests external-delivery payloads from the auth API,
delivers OTPs or one-time links locally, and strips those sensitive payloads before responding to the
browser.
OAuth And Organizations
Section titled “OAuth And Organizations”The server adapter proxies OAuth and organization routes so your browser app can stay on the same cookie boundary.
OAuth provider configuration belongs on seamless-auth-api, not in the adapter. Configure
LOGIN_METHODS to include oauth, add oauth_providers, and store provider client secrets in env
vars referenced by clientSecretEnv.
Organization routes let authenticated users create organizations, switch active organization, and
manage organization members. Admin organization routes sit under /auth/admin/organizations/*.
Admin Hardening Routes
Section titled “Admin Hardening Routes”The adapter proxies admin hardening endpoints used by the dashboard:
DELETE /auth/admin/sessions/by-id/:idDELETE /auth/admin/sessions/:userId/revoke-allPOST /auth/admin/users/:userId/recovery/device-replacement
Device replacement recovery requires a fresh step-up session in the auth API. It revokes sessions, removes passkeys, disables TOTP credentials, and returns counts only.
@seamless-auth/core
Section titled “@seamless-auth/core”The core package is lower level and framework-agnostic.
It is designed for:
- custom adapters
- future framework integrations
- runtimes where you want full control over request and response handling
Key exports include:
ensureCookies(...)refreshAccessToken(...)verifyCookieJwt(...)createServiceToken(...)authFetch(...)getSeamlessUser(...)hasScopedRole(...)- login, registration, OTP, magic-link, OAuth, session, and organization handlers
Core functions return descriptive result objects. They do not set Express responses, read environment variables, or own framework-specific behavior.
Current Official Coverage
Section titled “Current Official Coverage”Today, the strongest supported server-side path is Express.
The best-supported current combination is:
@seamless-auth/react or @seamless-auth/react-native -> your frontend
@seamless-auth/express -> your backend integration layer
seamless-auth-api -> your auth engineUse @seamless-auth/core when Express is not your runtime or you are building an adapter. The
Fastify adapter, @seamless-auth/fastify, mirrors the Express one, bearer transport included.