# OAuth & Identity Services — Google, Apple, Microsoft, Okta, Clerk

> The five emulators that pretend to be identity providers: what OAuth/OIDC flows each one covers, how the auto-login fallback bypasses the browser consent screen in CI, and what each package lives in.

- Repository: vercel-labs/emulate
- GitHub: https://github.com/vercel-labs/emulate
- Human wiki: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d
- Complete Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/llms-full.txt

## Source Files

- `packages/@emulators/google/src`
- `packages/@emulators/apple/src`
- `packages/@emulators/microsoft/src`
- `packages/@emulators/okta/src`
- `packages/@emulators/clerk/src`
- `packages/emulate/src/registry.ts`
- `skills/google/SKILL.md`
- `skills/apple/SKILL.md`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/@emulators/core/src/middleware/auth.ts](packages/@emulators/core/src/middleware/auth.ts)
- [packages/@emulators/google/src/routes/oauth.ts](packages/@emulators/google/src/routes/oauth.ts)
- [packages/@emulators/google/src/index.ts](packages/@emulators/google/src/index.ts)
- [packages/@emulators/google/src/entities.ts](packages/@emulators/google/src/entities.ts)
- [packages/@emulators/apple/src/routes/oauth.ts](packages/@emulators/apple/src/routes/oauth.ts)
- [packages/@emulators/apple/src/entities.ts](packages/@emulators/apple/src/entities.ts)
- [packages/@emulators/microsoft/src/routes/oauth.ts](packages/@emulators/microsoft/src/routes/oauth.ts)
- [packages/@emulators/microsoft/src/entities.ts](packages/@emulators/microsoft/src/entities.ts)
- [packages/@emulators/okta/src/routes/oauth.ts](packages/@emulators/okta/src/routes/oauth.ts)
- [packages/@emulators/okta/src/routes/users.ts](packages/@emulators/okta/src/routes/users.ts)
- [packages/@emulators/okta/src/entities.ts](packages/@emulators/okta/src/entities.ts)
- [packages/@emulators/clerk/src/routes/oauth.ts](packages/@emulators/clerk/src/routes/oauth.ts)
- [packages/@emulators/clerk/src/routes/sessions.ts](packages/@emulators/clerk/src/routes/sessions.ts)
- [packages/@emulators/clerk/src/entities.ts](packages/@emulators/clerk/src/entities.ts)
- [skills/google/SKILL.md](skills/google/SKILL.md)
- [skills/apple/SKILL.md](skills/apple/SKILL.md)
</details>

# OAuth & Identity Services — Google, Apple, Microsoft, Okta, Clerk

This page covers the five identity-provider emulators in the `vercel-labs/emulate` repository: Google, Apple, Microsoft, Okta, and Clerk. Each one impersonates a real SSO or OIDC provider so your app can complete login flows, issue tokens, and exercise user-management APIs — all without touching the real internet.

Every emulator shares a common pattern: it exposes the same HTTP paths as the real provider, issues real-looking JWTs (signed by an in-process key), and renders a minimal browser consent page with clickable user buttons. In CI or headless environments, a *fallback mechanism* eliminates even that click — any bearer token that arrives at the emulator is automatically accepted and mapped to the first seeded user. Understanding this two-mode design (interactive browser flow vs. headless token bypass) is the key to using these emulators effectively.

---

## How the Auto-Login Fallback Works

In a real browser flow the user visits the `/auth/authorize` page, clicks a user button, and gets redirected back with a code. In CI there is no browser, so the emulators support a second path: the `defaultFallback` mechanism.

Every service entry in the registry defines a `defaultFallback()` function that returns a hardcoded `AuthFallback` object:

```ts
// packages/emulate/src/registry.ts:140-143
defaultFallback(cfg) {
  const firstEmail = (cfg?.users as Array<...> | undefined)?.[0]?.email ?? "testuser@gmail.com";
  return { login: firstEmail, id: 1, scopes: ["openid", "email", "profile"] };
},
```

This fallback is passed to the core `authMiddleware`. When the middleware receives any `Authorization: Bearer <token>` header, it first looks the token up in the known token map. If it is **not found but is non-empty**, and a fallback user is configured, the middleware silently resolves the request as that fallback user instead of returning 401:

```ts
// packages/@emulators/core/src/middleware/auth.ts:98-108
let user = tokens.get(token);
if (!user && fallbackUser && token.length > 0) {
  debug("auth", "fallback user for unknown token", { login: fallbackUser.login, id: fallbackUser.id });
  user = { login: fallbackUser.login, id: fallbackUser.id, scopes: fallbackUser.scopes };
}
if (user) {
  c.set("authUser", user);
  c.set("authToken", token);
  c.set("authScopes", user.scopes);
}
```

**Practical consequence:** in your test suite, set any non-empty bearer token (e.g. `Bearer test_token`) and configure the emulator with a seed user. Every API call will succeed as that user without ever opening a browser.

```text
Browser flow (interactive):
  App → GET /oauth/authorize → consent UI → user click → code → POST /oauth/token → JWT

CI fallback (headless):
  App → any API call with Bearer <anything> → authMiddleware resolves fallback user → request succeeds
```

Sources: [packages/@emulators/core/src/middleware/auth.ts:64-112](), [packages/emulate/src/registry.ts:140-143]()

---

## Google — `@emulators/google`

**Package:** `@emulators/google` v0.6.0  
**Default port:** 4002  
**Registry label:** "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator"

Google is the most feature-rich of the five. It wraps three real Google product APIs in addition to OAuth itself.

### OAuth/OIDC Flow

| Endpoint | Real Google URL | Emulator path |
|---|---|---|
| OIDC Discovery | `accounts.google.com/.well-known/openid-configuration` | `/.well-known/openid-configuration` |
| JWKS | `oauth2.googleapis.com/oauth2/v3/certs` | `/oauth2/v3/certs` |
| Authorization | `accounts.google.com/o/oauth2/v2/auth` | `/o/oauth2/v2/auth` |
| Token exchange | `oauth2.googleapis.com/oauth2/token` | `/oauth2/token` |
| Userinfo | `oauth2.googleapis.com/oauth2/v2/userinfo` | `/oauth2/v2/userinfo` |
| Revocation | `oauth2.googleapis.com/oauth2/revoke` | `/oauth2/revoke` |

The emulator issues **HS256** id_tokens (signed with a static secret `"emulate-google-jwt-secret"`) rather than RS256, which simplifies key management locally. PKCE (both `plain` and `S256`) and `client_secret_basic` / `client_secret_post` authentication are both supported for token exchange. The `hd` (hosted domain) claim is automatically derived from the user's email domain; consumer domains (`gmail.com`, `googlemail.com`) have it omitted.

Sources: [packages/@emulators/google/src/routes/oauth.ts:64-122]()

### Gmail, Calendar, and Drive

Google goes far beyond just login. Three additional route groups are mounted:

| API surface | Key endpoints |
|---|---|
| **Gmail messages** | List, get, insert, import, send, modify, batchModify, batchDelete, trash, untrash, delete, attachments |
| **Gmail drafts** | List, get, create, update (PUT), send, delete |
| **Gmail threads** | List, get, modify, trash, untrash, delete |
| **Gmail labels** | List, get, create, update (PUT/PATCH), delete |
| **Gmail history** | List history, watch, stop |
| **Gmail settings** | Filters, forwarding addresses, send-as |
| **Calendar** | calendarList, events (list/create/delete with timeMin/timeMax), freeBusy |
| **Drive** | files list (query syntax), create (JSON + multipart), get (`alt=media` download), update with addParents/removeParents |

The seed config accepts `messages`, `calendars`, `calendar_events`, and `drive_items` keys so tests start with realistic data already in place.

Sources: [packages/@emulators/google/src/index.ts](), [skills/google/SKILL.md]()

---

## Apple — `@emulators/apple`

**Package:** `@emulators/apple` v0.6.0  
**Default port:** 4000 (standalone) / configurable  
**Registry label:** "Apple Sign In / OAuth emulator"

Apple's "Sign in with Apple" is OIDC-based but has several quirks that the emulator faithfully replicates.

### OAuth/OIDC Flow

| Endpoint | Real Apple URL | Emulator path |
|---|---|---|
| OIDC Discovery | `appleid.apple.com/.well-known/openid-configuration` | `/.well-known/openid-configuration` |
| JWKS | `appleid.apple.com/auth/keys` | `/auth/keys` |
| Authorization | `appleid.apple.com/auth/authorize` | `/auth/authorize` |
| Token exchange | `appleid.apple.com/auth/token` | `/auth/token` |
| Revocation | `appleid.apple.com/auth/revoke` | `/auth/revoke` |

Apple uses **RS256** — the emulator generates a fresh RSA key pair at module load time (`kid: "emulate-apple-1"`) and exposes it via JWKS. The discovery document reports `subject_types_supported: ["pairwise"]`, exactly matching the real provider.

### Apple-Specific Behaviors Emulated

**`response_mode` support:** Apple supports `query`, `fragment`, and `form_post` redirect modes. The emulator handles all three, rendering a full HTML form for `form_post` responses — important because most server-side apps use form_post.

**First-authorization `user` JSON blob:** Real Apple only sends the user's name/email in the authorization response on the *first* login ever. The emulator tracks this with an in-memory `firstAuthTracker` Set (keyed `email:clientId`) and sends the JSON blob only on the first authorization, then never again.

**Private Relay Email:** Users seeded with `is_private_email: true` get a `@privaterelay.appleid.com` address in their id_token, mirroring Apple's email privacy feature.

**Refresh tokens:** Token refresh issues a new `access_token` and `id_token` but no new `refresh_token` — matching Apple's real behavior.

```ts
// packages/@emulators/apple/src/entities.ts
interface AppleUser {
  is_private_email: boolean;
  private_relay_email: string; // @privaterelay.appleid.com address
  real_user_status: number;    // 0/1/2
  // ...
}
```

Sources: [packages/@emulators/apple/src/routes/oauth.ts:19-68](), [packages/@emulators/apple/src/entities.ts](), [skills/apple/SKILL.md]()

---

## Microsoft — `@emulators/microsoft`

**Package:** `@emulators/microsoft` v0.6.0  
**Registry label:** "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator"

The Microsoft emulator covers Entra ID (formerly Azure AD) including both the v2.0 OIDC endpoints and the legacy v1.0 endpoint.

### OAuth/OIDC Flow

| Endpoint | Emulator path | Notes |
|---|---|---|
| Global discovery | `/.well-known/openid-configuration` | |
| Tenant-scoped discovery | `/:tenant/v2.0/.well-known/openid-configuration` | Supports `common`, `organizations`, or actual tenant ID |
| JWKS | `/discovery/v2.0/keys` | RS256 |
| Authorization | `/oauth2/v2.0/authorize` | |
| Token exchange (v2) | `/oauth2/v2.0/token` | auth_code, refresh_token, client_credentials |
| Token exchange (v1) | `/:tenant/oauth2/token` | translates `resource` → `scope` |
| Userinfo | `/oidc/userinfo` | |
| Microsoft Graph `/me` | `/v1.0/me` | Returns OIDC claims in Graph format |
| User by ID | `/v1.0/users/:id` | |
| Logout | `/oauth2/v2.0/logout` | |

The emulator handles **`client_secret_basic`** (credentials in the `Authorization` header) in addition to `client_secret_post`, and the v1 endpoint translates the `resource` parameter into scopes so apps targeting older ADAL-style flows work without changes.

Users carry a `tenant_id` field (defaulting to `"9188040d-6c67-4c5b-b112-36a304b66dad"`, the well-known consumer tenant) and an `oid` (object ID) generated as a UUID, matching the real Entra ID claim shape.

Sources: [packages/@emulators/microsoft/src/routes/oauth.ts](), [packages/@emulators/microsoft/src/helpers.ts]()

---

## Okta — `@emulators/okta`

**Package:** `@emulators/okta` v0.6.0  
**Registry label:** "Okta OAuth 2.0 / OpenID Connect + management API emulator"

Okta is the most structurally complex emulator because Okta itself is a full identity platform with multiple authorization servers, group management, and an extensive management API.

### Multi-Server Architecture

Okta supports multiple *authorization servers*, each with its own issuer URL and JWKS. The emulator replicates this:

- **Org auth server** (`org`): `<base>/oauth2/org/…` — used for Okta admin API tokens
- **Default auth server** (`default`): `<base>/oauth2/default/…` — the typical app-facing server with audience `api://default`
- **Custom servers**: any `auth_server_id` seeded in config gets its own discovery document and JWKS

Each server exposes: `/.well-known/openid-configuration`, JWKS, `/authorize`, `/token` (auth_code + refresh_token + **client_credentials**), `/userinfo`, `/revoke`, `/introspect`, `/logout`.

### Token Introspection

The `/introspect` endpoint returns an RFC 7662-compliant response:

```json
{ "active": true, "sub": "testuser@okta.local", "scope": "openid profile email", ... }
```

Tokens not found in the store return `{ "active": false }`.

### Groups Claim

When the `groups` scope is requested, the id_token includes a `groups` array claim populated from the user's actual group memberships in the store — important for testing role-based access controls.

### Management API

Beyond OAuth, Okta exposes a full management REST API:

| Resource | Endpoints |
|---|---|
| Users | CRUD + lifecycle (activate/deactivate/suspend/unsuspend/reactivate) |
| Groups | CRUD + member management |
| Apps | CRUD + lifecycle + user assignments |
| Authorization Servers | CRUD + lifecycle; deletes cascade to oauth_clients |

Two-step user deletion matches real Okta: the first `DELETE /api/v1/users/:id` call deactivates the user; the second call removes them.

The default fallback uses `login` or `email` from the first seeded user, with scopes `["openid", "profile", "email", "groups"]`.

Sources: [packages/@emulators/okta/src/routes/oauth.ts](), [packages/@emulators/okta/src/routes/users.ts](), [packages/@emulators/okta/src/helpers.ts](), [packages/emulate/src/registry.ts:359-365]()

---

## Clerk — `@emulators/clerk`

**Package:** `@emulators/clerk` v0.6.0  
**Registry label:** "Clerk authentication and user management emulator"

Clerk differs from the other four: it is not a raw OIDC provider but an authentication-as-a-service platform. Its emulator therefore covers both the OIDC/OAuth surface *and* Clerk's Backend API.

### OAuth/OIDC Flow

| Endpoint | Emulator path | Notes |
|---|---|---|
| OIDC Discovery | `/.well-known/openid-configuration` | |
| JWKS | `/v1/jwks` | RS256 |
| Authorization | `/oauth/authorize` | Supports PKCE S256/plain |
| Token exchange | `/oauth/token` | auth_code only; creates a session record |
| Userinfo | `/oauth/userinfo` | Returns `sid`, `email`, `email_verified`, `name` |

Unlike the others, Clerk's token endpoint creates a **session record** in the store each time a code is exchanged. Session tokens issued via `/v1/sessions/:id/tokens` include organization claims (`org_id`, `org_role`, `org_slug`, `org_permissions`) from the user's first active membership.

### Backend API (Secret Key)

All Backend API endpoints require a `sk_test_` or `sk_live_` secret key in the `Authorization: Bearer` header. This matches Clerk's production authentication model for server-side code.

| Resource | Endpoints |
|---|---|
| Users | CRUD, ban/unban, lock/unlock, metadata PATCH, verify_password |
| Email addresses | CRUD; promoting a primary demotes all others |
| Organizations | CRUD, metadata PATCH; creator auto-gets admin membership |
| Memberships | CRUD, metadata PATCH; `org:admin` role gets 6 permissions by default |
| Invitations | List, get, create, bulk create, revoke; tracks `pending_invitations_count` |
| Sessions | List, get, revoke, issue tokens (with org claims), template tokens |

The Clerk `defaultFallback` picks the first email address from the first seeded user:

```ts
// packages/emulate/src/registry.ts:471-475
defaultFallback(cfg) {
  const firstEmail =
    (cfg?.users as Array<{ email_addresses?: string[] }> | undefined)?.[0]?.email_addresses?.[0]
    ?? "test@example.com";
  return { login: firstEmail, id: 1, scopes: [] };
},
```

Sources: [packages/@emulators/clerk/src/routes/oauth.ts](), [packages/@emulators/clerk/src/routes/sessions.ts](), [packages/@emulators/clerk/src/route-helpers.ts](), [packages/emulate/src/registry.ts:463-504]()

---

## Side-by-Side Comparison

| | Google | Apple | Microsoft | Okta | Clerk |
|---|---|---|---|---|---|
| **npm package** | `@emulators/google` | `@emulators/apple` | `@emulators/microsoft` | `@emulators/okta` | `@emulators/clerk` |
| **Default port** | 4002 | 4000 | — | — | — |
| **JWT algorithm** | HS256 | RS256 | RS256 | RS256 | RS256 |
| **PKCE support** | S256, plain | — | — | — | S256, plain |
| **client_credentials grant** | No | No | Yes | Yes | No |
| **Token introspection** | No | No | No | Yes | No |
| **User management API** | No | No | Graph `/me` only | Full (users/groups/apps) | Full (Backend API) |
| **Extra APIs** | Gmail, Calendar, Drive | — | — | Multi auth-server | Organizations, Sessions |
| **Special quirks** | `hd` claim, workspace domains | first-auth blob, private relay, form_post | v1 `resource` → `scope` | multi-server, groups claim, 2-step delete | session records, org claims |

Sources: [packages/emulate/src/registry.ts:132-504]()

---

## Seeding and Configuration

All five emulators accept a YAML/JSON seed config that maps directly onto their store. Each one exposes a `seedFromConfig(store, baseUrl, config)` function called at startup. The minimum viable config for each:

```yaml
# Google
google:
  users:
    - email: "dev@example.com"
      name: "Dev User"
      email_verified: true
  oauth_clients:
    - client_id: "my-client.apps.googleusercontent.com"
      client_secret: "GOCSPX-secret"
      redirect_uris: ["http://localhost:3000/api/auth/callback/google"]

# Apple
apple:
  users:
    - email: "dev@icloud.com"
      name: "Dev User"
  oauth_clients:
    - client_id: "com.example.app"
      team_id: "TEAM001"
      redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]

# Microsoft
microsoft:
  users:
    - email: "dev@contoso.com"
      name: "Dev User"
  oauth_clients:
    - client_id: "my-app-client-id"
      client_secret: "my-secret"
      redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]

# Okta
okta:
  users:
    - login: "dev@okta.local"
      email: "dev@okta.local"
  authorization_servers:
    - id: "default"
      name: "default"
      audiences: ["api://default"]
  oauth_clients:
    - client_id: "my-okta-client"
      client_secret: "my-secret"
      auth_server_id: "default"
      redirect_uris: ["http://localhost:3000/callback"]

# Clerk
clerk:
  users:
    - first_name: "Dev"
      last_name: "User"
      email_addresses: ["dev@example.com"]
  oauth_applications:
    - client_id: "clerk_client"
      client_secret: "clerk_secret"
      redirect_uris: ["http://localhost:3000/api/auth/callback/clerk"]
```

Sources: [packages/emulate/src/registry.ts:144-504]()

---

## Summary

The five identity emulators cover the most common SSO providers used in web applications. Google adds an unusually broad API surface (Gmail, Calendar, Drive) on top of standard OIDC. Apple accurately replicates the quirks of "Sign in with Apple" — form_post mode, first-login user blobs, and Private Relay emails. Microsoft handles both the modern v2.0 and legacy v1.0 Entra ID endpoints including `client_credentials`. Okta stands out with multi-authorization-server support, token introspection, and a full management REST API. Clerk treats authentication as a full service layer, combining OIDC with organizations, sessions, and a secret-key-authenticated Backend API. In every case, the `defaultFallback` in `authMiddleware` lets CI pipelines skip the browser entirely by treating any non-empty bearer token as the first seeded user. Sources: [packages/@emulators/core/src/middleware/auth.ts:70-112]()
