Single Sign-On (SSO)
OAuth/OIDC sign-in with automatic cross-domain support
EntityAuth provides shared SSO - one OAuth app (Google, GitHub) works for all tenants automatically. No per-tenant configuration needed!
Shared SSO Setup (One-Time)
As the EntityAuth admin, configure these environment variables once and all tenants get SSO:
# Google OAuth (create at https://console.cloud.google.com)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# GitHub OAuth (create at https://github.com/settings/developers)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# Token encryption
EA_TOKEN_ENC_KEY=your-32-byte-hex-key
EA_TOKEN_ENC_KEY_VERSION=1
OAuth Redirect URIs:
- Google:
https://your-entity-auth-domain.com/api/auth/sso/callback - GitHub:
https://your-entity-auth-domain.com/api/auth/sso/callback
That's it! All workspaces now have Google and GitHub SSO.
Same-Domain SSO (Easiest)
If your app is on the same domain as EntityAuth (e.g., both on entity-auth.com):
Use the AuthForm component
import { AuthForm } from '@entity-auth/components';
<AuthForm
client={ea}
workspaceTenantId="your-tenant"
redirectTo="/dashboard"
/>The user clicks "Sign in with Google", completes OAuth, and is redirected back with a session cookie. It just works!
Cross-Domain SSO (Different Domain)
If your app is on a different domain (e.g., your-app.com using EntityAuth on entity-auth.com):
EntityAuth uses server-side ticket exchange via Next.js middleware. This ensures clean authentication flow with zero page reloads.
Add Entity Auth middleware
For Next.js 13+ apps, create middleware.ts in your app root:
import { entityAuthMiddleware } from '@entityauth/nextjs/middleware';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
return entityAuthMiddleware(request, {
entityAuthUrl: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL || 'https://entity-auth.com',
workspaceTenantId: process.env.NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID || '',
protectedRoutes: ['/dashboard/*', '/profile'],
publicRoutes: ['/'],
loginUrl: '/auth',
});
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};What this does:
Detects SSO redirect with __ea_ticket parameter, exchanges ticket for refresh token server-side (before page loads), sets cookie on your domain, redirects to clean URL, protects authenticated routes. Zero client-side complexity!
Update your AuthProvider
Ensure your auth provider seeds the refresh token from the cookie:
import { EntityAuthClient } from '@entityauth/auth-client';
const ea = new EntityAuthClient({
baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});
useEffect(() => {
// Seed refresh token from cookie (set by middleware)
const cookieMatch = document.cookie.match(/(?:^|;\s*)ea_refresh=([^;]+)/);
const cookieToken = cookieMatch?.[1];
if (cookieToken) {
localStorage.setItem("ea_refresh_token", cookieToken);
ea.setRefreshToken(cookieToken);
}
}, []);Add SSO buttons to your login page
async function loginWithGoogle() {
try {
const { authorizationUrl } = await client.startSSO({
provider: "google",
returnTo: "/dashboard",
});
window.location.href = authorizationUrl;
} catch (e) {
const error = e as Error;
alert(`Google sign-in error: ${error.message}`);
}
}
async function loginWithGitHub() {
try {
const { authorizationUrl } = await client.startSSO({
provider: "github",
returnTo: "/dashboard",
});
window.location.href = authorizationUrl;
} catch (e) {
const error = e as Error;
alert(`GitHub sign-in error: ${error.message}`);
}
}Flow:
- User clicks "Sign in with Google"
client.startSSO()calls Entity Auth service (uses configured baseURL)- Completes OAuth on EntityAuth
- EntityAuth redirects to
https://your-app.com/dashboard?__ea_ticket=abc123 TicketExchangeHandlerdetects the ticket and exchanges it for a refresh token- Refresh token is stored and user is authenticated!
Mobile SSO (iOS/Swift)
Mobile apps use the same ticket-based flow. See the Swift SDK docs for details.
let sso = EntityAuthSSO(baseURL: URL(string: "https://entity-auth.com")!)
let result = try await sso.signIn(
provider: "github",
returnTo: URL(string: "yourapp://auth")!,
workspaceTenantId: "your-tenant"
)
// result = (accessToken, refreshToken, sessionId, userId)
Security
- ✅ PKCE enforced (S256 method)
- ✅ State validation with tenant prefix
- ✅ Nonce validation for OpenID Connect
- ✅ Tickets expire in 60 seconds
- ✅ Single-use tickets (can't be replayed)
- ✅ Workspace-scoped tickets
Supported Providers
- Google (OpenID Connect)
- GitHub (OAuth 2.0)
Want to add more? Implement a provider adapter with buildAuthUrl, exchangeCode, and getUserProfile in src/lib/sso/providers/.