Skip to content

OAuth providers

OAuth

An app session identifies your signed-in user. A provider grant lets your server call a provider’s API. Configure both through an OAuth strategy in Auth.make. Your app owns accounts, claims, and permission policy.

Your app needs Strategy
Sign in with an existing account link OAuth.make()
Sign in and retain provider API access in the same consent OAuth.make({ access: profile })
Create an account after provider verification OAuth.makeRegistration({ registration, registrationPolicy })
Link another login method OAuth.makeAccounts({ policy })
Connect an API to an already authenticated account OAuth.makeConnected({ policy })
apps/server/auth.ts
import { Auth, Sessions } from "@yielded/auth";
import { OAuth } from "@yielded/auth/strategies";
import * as GitHub from "@yielded/auth-openid-client/GitHub";
import { AuthApi } from "@app/domain/auth-contract";
import { clientId } from "./config";
const profile = GitHub.accessProfile({ clientId, scopes: ["read:user"] });
export const AppAuth = Auth.make(AuthApi, {
sessions: Sessions.stateful(),
strategies: { social: OAuth.make({ access: profile }) },
defaultStrategy: "social",
});

Omit access to discard provider tokens after verifying identity. With access, the strategy encrypts and saves the grant before completing authentication through the same session authority used by passwords, passkeys, and email. Choose stateful, stateless, or state-assisted sessions independently.

  1. Browser

    Start sign-in

    The contract's OAuth signIn action starts a single-use flow.

  2. Provider

    Sign in and consent

    The person signs in at the provider and approves the requested scopes.

  3. Yielded Auth

    Receive the callback

    /auth/{provider}/callback

    The HTTP adapter routes the provider's callback to the OAuth strategy.

  4. Provider

    Exchange the code

    The strategy makes a single code exchange. An unknown outcome never permits repeating it.

  5. Your storage

    Retain the grant

    Your storage resolves the existing account link and saves the encrypted provider grant.

  6. Yielded Auth

    Complete authentication

    Only after the grant is saved does shared authentication deliver the session, as it does for passwords and passkeys.

  7. Browser

    Read the result

    The browser receives a session or a request for additional authentication. Provider tokens never enter browser results.

Declare the OAuth actions, then configure Http.make with GitHub.provider({ clientId, clientSecret, access: [profile] }). The HTTP adapter owns callback routing and private cookie delivery. Supply shared sign-in and connected persistence, account claims, and separate transaction/token keyrings through Layers. The runnable composition shows the full setup; GitHub and Strava provide concrete configuration and a single-account allowlist.

Retained sign-in currently requires an existing local account link. For new users, use registration first, then connect provider access after authentication.

Under the HTTP middleware, use the regular Auth API:

const auth = yield* AppAuth;
const session = yield* auth.requireSession();
const connections = yield* auth.listAccountConnections({ limit: 20 });

The server-side connected service handles refresh and token use:

const access = yield* AppAuth.strategies.social.access.ConnectedAccess;
yield* access.withAccessToken(invocation, { grantId, profileKey: profile.key }, readProfile);

Provide AppAuth.strategies.social.access.accessLayer with the same connected services. invocation must come from a verified session or a trusted job authority; the service rechecks the subject, grant, and application permission. readProfile receives a redacted token and is never retried by the library. Refresh does not upgrade session assurance. Provider tokens never enter browser results or sessions.

auth.disconnectAccount uses the same connected-grant authority. Provider revocation depends on the profile; cohort revocation needs the shared maintenance worker. See retained access for dependencies and recovery.

OAuthServer lets a signed-in user grant a registered MCP client access to your application. Auth supplies the application session; the OAuth strategy owns retained upstream provider credentials. The two grants stay separate:

Separate MCP and provider grants. The browser signs in through your login page and approves the MCP client on the OAuthServer consent page. The MCP client sends its MCP token to Effect McpServer, where the OAuthServer middleware supplies CurrentAccess to your tool handler. When the handler needs provider data, the OAuth strategy uses its provider grant to call the provider API. The MCP grant and the provider grant stay separate.
  • External: Browser
  • Your code: Your login pageloginPath
  • Yielded Auth: ConsentOAuthServer.make(…)
  • External: MCP client
  • External: Effect McpServeroauth.middleware(…)
  • Your code: Your tool handlerOAuthServer.CurrentAccess
  • Yielded Auth: OAuth strategyOptional provider access
  • External: Provider API
  • Browser to Your login page
  • Your login page to Consent
  • Consent to MCP client
  • MCP client to Effect McpServer: MCP token
  • Effect McpServer to Your tool handler
  • Your tool handler to OAuth strategy
  • OAuth strategy to Provider API
  • MCP grant: Consent, MCP client, Effect McpServer
  • Provider grant: OAuth strategy, Provider API

Define supported scopes, supply an identity service that verifies your existing session, and mount the authorization routes beside Effect’s MCP routes:

const oauth = OAuthServer.make("mcp", { scopes: ["athlete:read"] });
const protectedMcp = McpServer.toolkit(toolkit).pipe(
Layer.provide(handlers),
Layer.provide(
McpServer.layerHttp({
name: "Athlete tools",
version: "1.0.0",
path: "/mcp",
protocols: [McpProtocol.v2026_07_28],
}),
),
Layer.provide(oauth.middleware(["athlete:read"]).layer),
);

The runnable Strava MCP example provides the login, SQL migration, signing keys, client registration, CORS, and server. It uses the built-in consent page and a single allowlisted athlete; its tool returns the authenticated subject without calling Strava’s API.

Inside a tool handler, read OAuthServer.CurrentAccess; reject undefined. The value contains the authenticated subjectId, clientId, resource, scopes, and grant ID. Your application still decides which accounts and operations that subject may access. Your application owns the subject-to-provider connection mapping. Resolve that connection from trusted storage, then call withAccessToken on the strategy’s access.ConnectedAccess service; MCP clients never receive provider tokens.

This initial server supports explicitly registered public clients. Clients must support supplying their registered client ID; there is no dynamic registration or Client ID Metadata Document endpoint. See the authorization server reference for the setup and token lifecycle.

OpenIdClient.provider supports OIDC discovery and plain OAuth endpoints. Its optional access settings declare permission profiles and the provider’s refresh/resource/revocation contract. A custom ProviderDefinition.configure returns the sign-in protocol and optionally a connected protocol. Configuration, secrets, and required services remain in the provider Layer. See provider configuration.

The combined login example shares sessions across email, GitHub, and Google. Provider display metadata never authorizes linking accounts by matching email addresses.