Plugins

OAuth

Official plugin — social sign-in with eight providers under /auth, Authorization Code + PKCE only.

oauth() adds social login to Gate auth. Start at /auth/oauth/{provider}/start, approve, then land on /auth/oauth/callback/{provider} with a session — eight providers.

The one rule

Enable gate.auth, then .plug(oauth({ providers: { ... } })). Every provider runs Authorization Code + PKCE with an exact registered redirect URI — implicit and password grants do not exist here.

Quick start

Plug it

src/app.ts
import { oke } from "okengine";
import { oauth } from "okengine/plugins";

export const app = oke({
  name: "shop",
  env: "dev",
  gate: { auth: {} },
}).plug(
  oauth({
    baseUrl: "https://app.example.com",
    providers: {
      google: { enabled: true },
      github: { enabled: true },
    },
  }),
);

baseUrl synthesizes {baseUrl}/auth/oauth/callback/{provider} — register that exact string. Client secrets resolve from Vault (OAUTH_GOOGLE_CLIENT_SECRET, OAUTH_GITHUB_CLIENT_SECRET, …).

Start the flow

const { data } = await api.auth.oauthStart({ provider: "google" });
// redirect the browser to data.authorizationUrl

The response carries authorizationUrl, expiresInMs, and the provider echo. The flow row (state hash, PKCE verifier, nonce) lives for ten minutes.

Callback issues a session

The provider redirects to your callback route; the flow verifies state, exchanges the code, and signs the person in:

const body = {
  accessToken: "...",
  refreshToken: "...",
  userId: "usr_...",
};

A brand-new visitor gets a fresh account. Someone whose email already belongs to another credential is refused with email_in_use.

Providers

ProviderShapeIdentity sourceEmail verified
GoogleOIDCJWKS-verified ID tokenemail_verified claim
AppleOIDCJWKS-verified ID tokenstrict parse — "false" stays false
MicrosoftOIDCJWKS-verified ID tokenemail_verified claim
GitHubOAuth2/user + /user/emailsprimary verified flag
DiscordOAuth2/users/@meverified flag
XOAuth2/2/users/menever — no trustworthy signal
FacebookOAuth2Graph /menever — no trustworthy signal
FigmaOAuth2/v1/menever — no verification field

Each provider has its own page under this category — endpoints, scopes, setup, and trust rules.

Security model

ThreatDefense
Code interceptionPKCE S256 on every provider
CSRF / forged callbackssingle-use state, SHA-256-hashed at rest
Mix-up across providers (RFC 9700)per-provider callback routes + issuer pinning on every assertion
Unverified-email takeoverlinkOrProvision refuses email_in_use without an authenticated owner
Redirect manipulationbyte-exact stored redirect_uri at token exchange

Consequence: an attacker who completes a social login claiming your email gains nothing — the account stays untouched unless they already own a session for it.

Options

OptionTypeDefaultMeaning
providersmap{}Per-provider config, disabled by default
baseUrlstringOrigin for synthesized callback URIs
secretstringactive*HMAC secret (*from gate.auth)
sessionsSessionStoreactive*Session store
identitiesIdentityStoreactive*Shared user store
verificationsVerificationStorenewFlow-state store
fetchtypeof fetchglobalInjectable transport (tests)

Per-provider config: enabled, clientId, redirectUri, scopes, storeProviderTokens, plus Microsoft tenant and Apple teamId / keyId.

Surfaces

FlowPathGate
auth.oauthStartPOST /auth/oauth/{provider}/startgate.public + otp rate
auth.oauthCallbackGET+POST /auth/oauth/callback/{provider}gate.public + otp rate
auth.oauthLinkStartPOST /auth/oauth/{provider}/linksession + bearer

The GET + POST callback pair exists because Apple posts its response instead of redirecting.

Troubleshooting

Learn more

Next

On this page