Overview

Prev Next

OAuth Integration

Social sign-in via external identity providers. The system uses a factory-based architecture so new providers can be added without changing the auth pipeline. Currently ships Google and Microsoft, both gated by feature flags.

Architecture

Image

Source Layout

src/
  lib/external/oauth/
    types.ts              # SocialAuthProvider interface, SocialProvider type, VerifiedIdentity
    factory.ts            # SocialAuthFactory — maps provider key to class instance
    providers/
      google.ts           # GoogleProvider — Authorization Code flow with Google
      microsoft.ts        # MicrosoftProvider — Authorization Code flow with Microsoft
    GoogleOAuth.ts        # Legacy (unused) — superseded by provider pattern
  app/api/identity/oauth/
    [provider]/
      init/route.ts       # GET — flag check, factory, build auth URL, set state cookie
      callback/route.ts   # GET — validate state, exchange code, redirect with token hash
  services/
    IdentityService.ts    # initOAuthLogin(), handleOAuthCallback() — client-side orchestration
  hooks/auth/
    useAuth.ts            # oauthInit(), handleOAuthCallback() — React wrapper
  modules/authentication/signin/social/
    index.tsx             # SocialLoginModule — reads hash, dispatches callback
    google/index.tsx      # GoogleSignin button
    microsoft/index.tsx   # MicrosoftSignin button
  graphql/mutations/identity/
    googleLogin.mutation.ts       # googleLogin(token, deviceId)
    microsoftLogin.mutation.ts    # microsoftLogin(token, deviceId)
  config/identity/
    oauth.config.ts       # OAuthConfig — env-var-backed config (server-only)
  types/identity/
    index.ts              # OAuthProviderEnum, related auth types

Configuration

All OAuth config is server-only (guarded by import 'server-only').

Google

Key Env Var Default
clientId GOOGLE_OAUTH_CLIENT_ID
clientSecret CLIENT_SECRET
redirectUris GOOGLE_OAUTH_REDIRECT_URIS
authUri GOOGLE_OAUTH_AUTH_URI https://accounts.google.com/o/oauth2/v2/auth
tokenUri GOOGLE_OAUTH_TOKEN_URI https://oauth2.googleapis.com/token
profileUri GOOGLE_OAUTH_PROFILE_URI https://www.googleapis.com/oauth2/v1/userinfo

Microsoft

Key Env Var Default
clientId MS_CLIENT_ID
clientSecret MS_CLIENT_SECRET
redirectUris MS_OAUTH_REDIRECT_URIS
tenantId MS_TENANT_ID

Feature Flags

Both providers are independently gated:

Flag (raw) UI key Controls
enable-google-signin showGoogleSignin Google button visibility + init route
enable-microsoft-signin showMicrosoftSignin Microsoft button visibility + init route

Flags are checked in two places:

  1. ClientSocialLoginModule reads flags from FeatureFlagContext to conditionally render buttons.
  2. Server — the /init route calls FeatureFlagService.list() and returns 404 if the provider is disabled.

Types

OAuthProviderEnum

enum OAuthProviderEnum {
  Google = 'google',
  Microsoft = 'microsoft',
}

SocialProvider

type SocialProvider = OAuthProviderEnum.Google | OAuthProviderEnum.Microsoft;

SocialAuthProvider

The interface every provider must implement:

interface SocialAuthProvider {
  getAuthorizationUrl(state?: string): string;
  exchangeCodeForIdToken(code: string): Promise<string>;
}

VerifiedIdentity

Data shape returned after successful token verification (reserved for future use with direct profile parsing):

interface VerifiedIdentity {
  provider: SocialProvider;
  providerUserId: string;
  email: string;
  emailVerified: boolean;
  firstName?: string;
  lastName?: string;
  picture?: string;
}

Error Handling

All OAuth errors surface through the AppError system using the auth.oauth_failed code.

Error Code Status Default Message
auth.oauth_failed 401 OAuth authentication failed
config.missing Provider configuration missing
config.graphql_query_missing GraphQL mutation not found
system.not_implemented Unsupported provider

Provider classes throw granular messages (e.g., "Unable to connect to Google OAuth service", "Invalid response from Microsoft OAuth service") which bubble up through AppError.create().

Design Decisions

Why Authorization Code flow, not Implicit? Authorization Code flow keeps the client_secret on the server. The callback route exchanges the code server-side, so secrets never reach the browser.

Why redirect with hash fragment? The callback route redirects to ${frontendUrl}#provider=...&token=.... Hash fragments are never sent to the server by the browser, so the id_token stays client-side during the redirect.

Why factory instead of a provider registry? The switch-based factory in a server-only file keeps the mapping static and tree-shakeable. A dynamic registry would add complexity without clear benefit at two providers.

Why feature-flag the init route? Double-gating (client + server) ensures a disabled provider cannot be invoked even if the UI flag check is bypassed.

Why GoogleOAuth.ts is still in the tree? Legacy file from the pre-factory implementation. It is not imported anywhere and can be removed.

What's in This Section

Doc Covers
Flow End-to-end request lifecycle from button click to tenant selection
Providers Factory, provider implementations, adding a new provider