LogoRobo.js

Upstream proxy

Forward authentication routes to another Robo instance for shared auth across multiple apps.

When multiple Robo apps need the same auth backend, configure upstream proxy mode. All /api/auth/* routes are forwarded to a canonical Robo instance. getServerSession and getToken also proxy to the upstream service.

Basic setup

config/plugins/robojs/auth.ts
import type { AuthPluginOptions } from '@robojs/auth'

export default <AuthPluginOptions>{
  basePath: '/api/auth',
  upstream: {
    baseUrl: process.env.AUTH_UPSTREAM_URL!,
    headers: { 'x-api-key': process.env.AUTH_PROXY_KEY! }
  }
}
config/plugins/robojs/auth.js

export default {
  basePath: '/api/auth',
  upstream: {
    baseUrl: process.env.AUTH_UPSTREAM_URL!,
    headers: { 'x-api-key': process.env.AUTH_PROXY_KEY! }
  }
}

In proxy mode, no adapter, providers, or email config is needed locally. The upstream handles everything.

Configuration options

OptionTypeDefaultDescription
baseUrlstringRequiredAbsolute URL of the upstream Robo instance
basePathstringLocal basePathAuth route prefix on the upstream
headersRecord<string, string>{}Extra headers sent with every proxied request
cookieNamestring'authjs.session-token'Session cookie name matching the upstream
secretstringLocal secretEnables local JWT decoding without network calls
sessionStrategy'jwt' | 'database'Local strategyMust match the upstream's strategy
fetchFetchLikeGlobal fetchCustom fetch for retries, SSR, or edge runtimes

How it works

The proxy behavior works like this:

  • All Auth.js routes (/providers, /session, /csrf, /signin/*, /signout, /callback/*, etc.) forward requests to the upstream
  • Request headers are cleaned: host, content-length, connection, accept-encoding are removed
  • Custom upstream.headers are merged into every request
  • POST request bodies are forwarded
  • Cookies are preserved for session forwarding
  • getServerSession() proxies to the upstream /session endpoint
  • getToken() proxies to upstream (or decodes locally if secret is provided)

Local JWT decoding

Providing upstream.secret enables local JWT decoding:

config/plugins/robojs/auth.ts
upstream: {
  baseUrl: 'https://auth.myapp.com',
  secret: process.env.AUTH_SECRET,
  sessionStrategy: 'jwt'
}
config/plugins/robojs/auth.js
upstream: {
  baseUrl: 'https://auth.myapp.com',
  secret: process.env.AUTH_SECRET,
  sessionStrategy: 'jwt'
}

This cuts network traffic roughly in half — getToken() decodes locally instead of calling the upstream. Without a secret, call getToken(request, { raw: true }) to get the raw cookie value, or use getServerSession() which always consults the upstream.

Cross-origin considerations

When the frontend and auth server run on different origins, set cookies with SameSite=None; Secure on the auth server, add the frontend origin to your CORS allowlist, and set AUTH_REDIRECT_PROXY_URL for OAuth callback handling.

config/plugins/robojs/auth.ts
// Frontend Robo app (proxy mode)
upstream: {
  baseUrl: 'https://auth.myapp.com',
  headers: { 'x-tenant': 'frontend-app' }
}
config/plugins/robojs/auth.js
// Frontend Robo app (proxy mode)
upstream: {
  baseUrl: 'https://auth.myapp.com',
  headers: { 'x-tenant': 'frontend-app' }
}

Client-side usage

Client helpers work the same way in proxy mode. They hit the local proxy routes which forward to upstream:

import { signIn, getSession } from '@robojs/auth/client'

// These go through the local proxy to upstream
await signIn('discord')
const session = await getSession()
import { signIn, getSession } from '@robojs/auth/client'

// These go through the local proxy to upstream
await signIn('discord')
const session = await getSession()

For direct cross-origin calls (bypassing the proxy), use the baseUrl option:

await signIn('discord', { callbackUrl: '/dashboard' }, { baseUrl: 'https://auth.myapp.com' }, true)
await signIn('discord', { callbackUrl: '/dashboard' }, { baseUrl: 'https://auth.myapp.com' }, true)

Next steps

On this page