AUTH4D-147 Epic Games sign-in
Epic Account Services documents the OpenID Connect Core authorization code flow for websites, so
Epic Games is a fourth preset of the AUTH4D-146 (repository file docs/api-spec/auth4d-146.md) OpenID Connect relying party,
named epic. Start, callback, state, PKCE, nonce, proofs, guest linking, device sign-in and project
settings all work as AUTH4D-146 describes. This fragment lists only what differs.
Each project registers its own Epic Account Services application and client in the Epic Developer Portal and stores the client ID and secret with auth4.dev. auth4.dev reads no Epic friends, presence, country or other Epic Account Services data.
| Preset | Issuer accepted in the ID token | Scopes | Response mode |
|---|---|---|---|
epic |
https://api.epicgames.dev/epic/oauth/v2 |
openid basic_profile |
query |
The endpoints are the ones Epic publishes in
https://api.epicgames.dev/epic/oauth/v2/.well-known/openid-configuration:
| Endpoint | URL |
|---|---|
| Authorization | https://www.epicgames.com/id/authorize |
| Token | https://api.epicgames.dev/epic/oauth/v2/token |
| JWKS | https://api.epicgames.dev/epic/oauth/v2/.well-known/jwks.json |
Routes
GET /t/{tenantId}/identity/epic/start?client_id={clientId}&browser_flow_id={flowId}GET /t/{tenantId}/identity/epic/callback
Both behave exactly as the Google and Microsoft routes in AUTH4D-146. There is no POST callback.
Differences from the other presets
- Scopes.
basic_profileis required for every Epic Account Services application;openidasks for the ID token. No other Epic permission is requested. - Account prompt. Epic gets no
promptparameter. The redirect carriesresponse_type=code,client_id,redirect_uri,scope,state,nonce,code_challengeandcode_challenge_method=S256. - Client authentication. Epic’s token endpoint takes the client credentials in an HTTP Basic
Authorizationheader (client_secret_basic), not in the form. The form carriesgrant_type=authorization_code,code,redirect_uriandcode_verifier. Epic does not document PKCE; auth4.dev sends the verifier anyway (a server ignores parameters it does not use, RFC 6749 §3.2), and the nonce and the one-time state protect the exchange either way. - ID token. The signature must verify with
RS256against Epic’s JWKS,issmust be exactlyhttps://api.epicgames.dev/epic/oauth/v2,audthe project’s Epic client ID, andexp,iat,subandnonceare checked as for the other presets. Epic’s own claims (appid,dn,pfpid,pfsid,pfdidand the optionalcty,eat,eadnandpltfm) are not stored. - Identity. The identity key is the ID token
sub, the player’s Epic Account ID, stored per tenant as(tenant_id, 'epic', sub). Epic sends no email address. Any email claim is ignored, so an Epic identity never carries one, and the database refuses an Epic identity with an email. A player who signs up with Epic Games starts without an email address and can add one later (AUTH4D-149 (repository filedocs/api-spec/auth4d-149.md)). Accounts are never merged. - Errors. A cancelled consent arrives as
access_denied. Other errors aretemporarily_unavailable, as for the other presets.
Proofs and login methods
The proof purpose is epic_login. POST /oauth/authorize/continue, the guest upgrade endpoints,
POST /device/sign-in/resume and the passkey registration options accept it exactly like
microsoft_login. Guest upgrades report identity.provider epic, and the guest.upgraded flow
event reports method epic.
login_methods in GET /t/{tenantId}/login/context and in the login_required answer of
GET /device/verify gains an optional epic key, present and true only when Epic Games is
enabled and configured:
{ "email": true, "discord": false, "steam": true, "epic": true }
Provider hint
provider_hint=epic on GET /oauth/authorize (AUTH4D-125 (repository file docs/api-spec/auth4d-125.md)) and provider=epic on
/login/upgrade offer Epic Games first. The browser SDK sends them with login({ provider: "epic" })
and upgradeGuest({ provider: "epic" }); the Unity package with
UpgradeGuestAsync(Auth4Provider.Epic, …). As before, a hint for a provider the application has
turned off shows the usual list.
Project settings
PATCH /management/tenants/{tenantId} accepts providers.epic. Every field is optional; unknown
fields are 400.
| Object | Fields |
|---|---|
epic |
enabled, clientId (16 to 64 letters and digits, from the portal’s Clients tab), clientSecret |
{
"providers": {
"epic": { "enabled": true, "clientId": "xyza7891lhxMVYGCON7LgnKZZ8HQGD5H", "clientSecret": "…" }
}
}
The rules for first configuration, changing the client ID and enabling are those of AUTH4D-146. The
secret is write-only, encrypted under the envelope purpose provider:epic:client_secret and stored
in oidc_provider_settings. The project view adds:
{
"epic": {
"enabled": true,
"clientId": "xyza7891lhxMVYGCON7LgnKZZ8HQGD5H",
"clientSecretConfigured": true,
"clientSecretUpdatedAt": "2026-10-07T09:00:00.000Z"
}
}
Each change writes a management.provider.updated audit event with provider epic and status.
Storage
Migration 0025_epic_games.sql rebuilds identities to allow provider = 'epic' with no email,
and rebuilds oidc_provider_settings to allow provider = 'epic'. Every stored row, key, index
and the password trigger are kept. See docs/operations/d1-schema.md.
Local development and tests
OIDC_LOCAL_MOCK_ORIGIN covers Epic too, with epic as the first path segment, for example
http://127.0.0.1:9100/epic/epic/oauth/v2/token. Tests never contact Epic.
packages/provider-oidc/src/index.test.ts: the Epic authorization URL, the Basic header, the exact issuer, and that an email claim is dropped.packages/db/src/epic-migration.test.ts: migration 0025 keeps every row and enforces the Epic rules.tests/integration/auth4d-147.epic.test.ts: a local mock of Epic’s token endpoint that requires the Basic header and PKCE. Covers browser sign-in, settings and secrets, the provider hint, guest upgrade and device sign-in.tests/e2e/auth4d-147.*.spec.ts(-c tests/e2e/auth4d-147.playwright.config.ts): the hosted login, provider hint, device page and guest upgrade buttons, and the console card, with a mocked API.
pnpm --filter @auth4/provider-oidc test
cd tests/integration && pnpm exec vitest run auth4d-147.epic.test.ts
Source: docs/api-spec/auth4d-147.md