ObjectStackObjectStack

Social & Enterprise SSO

Enable Google sign-in in open source, and extend auth providers through packages.

Social & Enterprise SSO

ObjectStack open source ships two built-in social sign-in implementations, both wired by os serve from deployment env vars:

  • Google OAuth: configure in Setup → Authentication, or from GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET deployment env vars
  • GitHub OAuth: configure from GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET deployment env vars

Additional providers should be contributed by product or enterprise packages through the auth:configure hook. Those packages can add better-auth socialProviders or OIDC/generic OAuth providers without forking @objectstack/plugin-auth.

The Studio login and registration pages automatically render Continue with ... buttons for every enabled provider returned by /api/v1/auth/config.


Social Providers

Google

  1. Go to Google Cloud ConsoleAPIs & ServicesCredentials.
  2. Click Create CredentialsOAuth 2.0 Client IDWeb application.
  3. Add an Authorized redirect URI:
    https://<your-domain>/api/v1/auth/callback/google
    For local development: http://localhost:3000/api/v1/auth/callback/google
  4. Copy the Client ID and Client Secret.

Then either save the values in Setup → Authentication → Social sign-in or provide them as deployment environment variables:

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

# Optional settings env override. UI settings are lower precedence than env.
# OS_AUTH_GOOGLE_ENABLED=false

GitHub

  1. Go to GitHub Developer SettingsOAuth AppsNew OAuth App.
  2. Set Authorization callback URL:
    https://<your-domain>/api/v1/auth/callback/github
  3. Copy the Client ID and generate a Client Secret.

Provide them as deployment environment variables:

GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

Extension Packages

The providers below are examples for enterprise or product packages, not built-in open-source settings. A package can register them by listening to auth:configure and mutating the draft auth config.

The env-var names shown below (e.g. MICROSOFT_CLIENT_ID, APPLE_CLIENT_ID) are illustrative for an extension package's own reader code — unlike GOOGLE_* and GITHUB_*, the framework does not read them out of the box, so an extension must wire them in its auth:configure handler.

Microsoft (Azure AD / Entra ID)

  1. Go to Azure PortalAzure Active DirectoryApp registrationsNew registration.
  2. Add a redirect URI (Web):
    https://<your-domain>/api/v1/auth/callback/microsoft
  3. Under Certificates & secrets, create a new client secret.
MICROSOFT_CLIENT_ID=your-azure-app-client-id
MICROSOFT_CLIENT_SECRET=your-azure-client-secret

# Optional: restrict to a single tenant (default: "common" — all Microsoft accounts)
# MICROSOFT_TENANT_ID=your-tenant-id

Apple

  1. Go to Apple Developer ConsoleCertificates, IDs & ProfilesIdentifiers → register a Services ID.
  2. Enable Sign In with Apple and configure the redirect URL:
    https://<your-domain>/api/v1/auth/callback/apple
  3. Generate a private key under Keys.
APPLE_CLIENT_ID=your-apple-services-id
APPLE_CLIENT_SECRET=your-apple-private-key-jwt

Apple requires the client secret to be a signed JWT derived from the private key. See better-auth Apple docs for details.


Enterprise SSO (OIDC)

Admin-managed OIDC SSO ships in the open framework: @objectstack/plugin-auth registers external IdPs through @better-auth/sso, and admins add them without code from Setup → SSO Providers (see the ADR-0069 note below). The oidcProviders config shown here is the in-process path for framework or product packages that prefer wiring providers in code, or contributing them through auth:configure.

Admin-managed external IdP (ADR-0069). Recent releases add a per-environment external-IdP path built on @better-auth/sso and surface an sso flag in the public /auth/config (features.sso) so a client can show an enterprise-login button when SSO is configured. Admins register providers without code from Setup → SSO Providers — both OIDC (Okta, Entra, Auth0, Keycloak, …) and SAML 2.0 (see SAML 2.0 below). The oidcProviders extension shown here remains the in-process path for framework/enterprise packages that prefer wiring providers in code.

Quick start — Okta

const oidcProviders = [
  {
    "providerId": "okta",
    "name": "Okta",
    "discoveryUrl": "https://your-org.okta.com/.well-known/openid-configuration",
    "clientId": "your-okta-client-id",
    "clientSecret": "your-okta-client-secret",
    "scopes": ["openid", "email", "profile"]
  }
];

Quick start — Azure AD (OIDC)

const oidcProviders = [
  {
    "providerId": "azure-ad",
    "name": "Azure AD",
    "discoveryUrl": "https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration",
    "clientId": "your-azure-client-id",
    "clientSecret": "your-azure-client-secret"
  }
];

Quick start — Keycloak

const oidcProviders = [
  {
    "providerId": "keycloak",
    "name": "Keycloak",
    "discoveryUrl": "https://keycloak.example.com/realms/your-realm/.well-known/openid-configuration",
    "clientId": "your-client-id",
    "clientSecret": "your-client-secret",
    "pkce": true
  }
];

Multiple enterprise providers

const oidcProviders = [
  {
    "providerId": "okta",
    "name": "Okta SSO",
    "discoveryUrl": "https://your-org.okta.com/.well-known/openid-configuration",
    "clientId": "...",
    "clientSecret": "..."
  },
  {
    "providerId": "azure-ad",
    "name": "Microsoft SSO",
    "discoveryUrl": "https://login.microsoftonline.com/<tenant>/v2.0/.well-known/openid-configuration",
    "clientId": "...",
    "clientSecret": "..."
  }
];

OIDC provider fields

FieldRequiredDescription
providerIdUnique identifier used internally (e.g. okta, azure-ad)
nameDisplay name shown on the button (defaults to providerId)
discoveryUrl✅*OIDC discovery URL — auto-fetches all endpoints
authorizationUrl✅*OAuth2 authorization endpoint (if no discoveryUrl)
tokenUrl✅*OAuth2 token endpoint (if no discoveryUrl)
userInfoUrlOAuth2 userinfo endpoint
issuerExpected issuer for token validation
clientIdOAuth2 client ID
clientSecretOAuth2 client secret
scopesRequested scopes (default: openid email profile)
pkceEnable PKCE — recommended for public clients

*Either discoveryUrl or authorizationUrl + tokenUrl must be provided.


Enterprise SSO (SAML 2.0)

SAML 2.0 is provided natively by @better-auth/sso (the same package behind the OIDC path) — no custom plugin or extension is needed. Admins register a SAML IdP from Setup → SSO Providers → Register SAML Provider, which posts to the env-side bridge at POST /api/v1/auth/admin/sso/register-saml.

Register a SAML provider

Fill in the IdP details collected by the Register SAML Provider action:

FieldRequiredDescription
providerIdStable identifier, e.g. acme-saml.
issuerThe IdP's SAML EntityID (issuer), e.g. https://saml.acme.com/entityid.
domainUsers with this email domain are routed to this IdP, e.g. acme.com.
entryPointThe IdP's SAML single sign-on (redirect) URL that receives the SAMLRequest.
certThe IdP's X.509 signing certificate (PEM body) — used to verify assertion signatures.
identifierFormatRequested SAML NameID format (defaults to the IdP's configured format), e.g. urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress.

On success the response returns the two Service Provider (SP) URLs you configure on the IdP side:

{
  "success": true,
  "data": { "providerId": "acme-saml" },
  "acsUrl": "https://<your-domain>/api/v1/auth/sso/saml2/sp/acs/acme-saml",
  "spMetadataUrl": "https://<your-domain>/api/v1/auth/sso/saml2/sp/metadata?providerId=acme-saml"
}
  • ACS (Assertion Consumer Service) URL — register this as the SP ACS / reply URL on the IdP.
  • SP metadata URL — serves the SP EntityDescriptor/SPSSODescriptor XML (many IdPs can import it directly to auto-configure the SP).

SAML sign-in flow

  1. User enters their work email (or clicks the enterprise-login button).
  2. Client calls POST /api/v1/auth/sign-in/sso with { email, callbackURL }.
  3. The email domain is matched to the registered provider; the response is a redirect to the IdP's entryPoint carrying a signed SAMLRequest.
  4. The IdP authenticates the user and POSTs a SAML assertion back to the SP ACS URL; @better-auth/sso (samlify) validates the signature/timestamp, then creates a session and redirects to callbackURL.

Signature, timestamp, and replay validation are handled by @better-auth/sso (samlify). You only supply the IdP's signing certificate at registration time.


Verify configuration

Restart the server (pnpm dev), then:

curl http://localhost:3000/api/v1/auth/config

Expected response (abbreviated — getPublicConfig() also returns disableSignUp/requireEmailVerification on emailPassword and additional features keys such as multiOrgEnabled, oidcProvider, deviceAuthorization, and admin):

{
  "emailPassword": { "enabled": true },
  "socialProviders": [
    { "id": "google", "name": "Google", "enabled": true, "type": "social" },
    { "id": "github", "name": "GitHub", "enabled": true, "type": "social" },
    { "id": "okta",   "name": "Okta SSO", "enabled": true, "type": "oidc" }
  ],
  "features": { "twoFactor": false, "passkeys": false, "magicLink": false, "organization": true }
}

The Studio /login and /register pages will now show a button for each enabled provider.


OAuth flow

Social providers (Google, GitHub, Microsoft, Apple):

  1. User clicks Continue with Google (or similar).
  2. Client calls POST /api/v1/auth/sign-in/social with { provider: "google", callbackURL }.
  3. Browser redirects to the provider's consent screen.
  4. Provider redirects to /api/v1/auth/callback/google.
  5. better-auth creates a session and redirects to callbackURL.

OIDC/enterprise providers:

  1. User clicks Continue with Okta SSO.
  2. Client calls POST /api/v1/auth/sign-in/oauth2 with { providerId: "okta", callbackURL }.
  3. Browser redirects to the provider's authorization endpoint.
  4. Provider redirects back; better-auth validates the OIDC token and creates a session.

Notes

  • OS_AUTH_SECRET must be set to a random string in production (a long random value of 32+ characters is recommended). AUTH_SECRET and BETTER_AUTH_SECRET are accepted as deprecated legacy aliases and emit a deprecation warning. If no secret is set, the auth plugin is skipped in production.
  • OS_AUTH_URL (or OS_BASE_URL as a fallback) must match the domain registered with each provider; it determines the base URL used to build OAuth callback URLs.
  • Never commit client secrets to source control — use a secrets manager in production.
  • The redirect URI registered with each provider must match exactly: https://<your-domain>/api/v1/auth/callback/<provider-id>.

On this page