Browser Client
Call the typed effect-auth HTTP contract from browser applications.
createAuthClient from @effect-auth/core/Client is the recommended Promise facade for browser UI. It validates inputs, calls the schema-defined AuthClientProtocolApi, decodes successful responses and public errors, and coordinates browser-only workflows such as passkeys. It does not discover application routes.
Create one client
Keep one client for the application lifetime. A Cloudflare app that proxies /auth/* to a private auth Worker needs no browser-visible backend URL:
import { createAuthClient } from "@effect-auth/core/Client";
export const auth = createAuthClient();| Option | Use |
|---|---|
baseUrl | Absolute auth origin; omit for same-origin requests |
requestInit | Defaults merged into each request; built-in default is { credentials: "include" } |
fetch | Custom transport for platform integration or tests |
browser.passkey | Overrides for browser credential operations |
protocol | Typed replacements, removals, and extensions |
The unified client owns an Effect managed runtime. Call await auth.dispose() only when its lifetime is shorter than the page or application.
Effect-native code can instead use makeAuthHttpClient. It returns the generated contract client in an Effect requiring HttpClient; the application then owns transport, interruption, tracing, and lifecycle.
import { makeAuthHttpClient } from "@effect-auth/core/Client";
import { Effect } from "effect";
import { FetchHttpClient } from "effect/unstable/http";
const session = Effect.gen(function* () {
const client = yield* makeAuthHttpClient();
return yield* client.session.current();
}).pipe(Effect.provide(FetchHttpClient.layer));Cookies and origins
Prefer same-origin /auth/*: cookies work with the defaults and a Cloudflare service binding can keep the auth Worker private. For a separate browser-visible origin, set baseUrl, keep credentials: "include", return exact credentialed CORS headers, and configure cookies for that topology.
| Concern | Responsibility |
|---|---|
Cookie Secure, SameSite, domain, path | Auth server configuration |
| Exact origin check | Mandatory effect-auth HTTP boundary |
| Optional double-submit lifecycle | Application |
| CORS and preflight responses | Application or edge boundary |
/auth/* service-binding proxy | Cloudflare application Worker |
Origin checks do not emit CORS headers. Every method except the exact case-sensitive tokens GET, HEAD, and OPTIONS needs an exact allowlisted Origin or Referer; lowercase and mixed-case spellings do not bypass the check. Fetch Metadata, Host, and forwarding headers are not substitutes. When both evidence headers are present, Origin is authoritative and a malformed Origin is rejected rather than falling back to Referer. Maintained APIs choose origin-only protection. If the app additionally mounts AuthCsrfMiddleware, it owns token entropy, delivery, rotation, expiry, and session binding. Never combine credentialed requests with Access-Control-Allow-Origin: *.
Methods and continuations
| Group | Representative operations |
|---|---|
password, email, emailOtp, magicLink | Start and complete primary authentication |
session | Current session, refresh, logout, list, revoke |
passkey, totp, recoveryCodes | Enroll, verify, list, revoke |
mfa, stepUp, loginApproval | Continue or strengthen authentication |
identities, emailVerification, security | Manage identities, verification, login reports |
Authentication may succeed without being complete. Sign-in results can be authenticated, requires_mfa, requires_email_verification, requires_login_approval, or requires_passkey_enrollment. These are successful continuation values, not exceptions; preserve their flowId and route the user to the matching method group.
Standalone clients such as createOAuthClient, createApiKeyClient, createJwtClient, and administrative clients expose narrower separately mounted contracts. Use them only when the unified browser contract intentionally does not contain that API.
Failures and cancellation
Standard operations accept { signal?: AbortSignal } as their final argument. Pass query-library cancellation through to interrupt Effect execution and fetch:
const sessionQuery = {
queryKey: ["session"],
queryFn: ({ signal }: { signal: AbortSignal }) =>
auth.session.currentOrUndefined({ signal }),
};currentOrUndefined converts only AuthUnauthenticatedError to undefined. Other failures reject. Narrow decoded public auth errors with isAuthApiError; network, schema/decode, browser, and custom-extension errors need an unknown fallback. Display only messages from trusted decoded public errors.
import {
authClientErrorMessage,
isAuthApiError,
} from "@effect-auth/core/Client";
try {
await auth.session.logout();
} catch (error) {
const message = isAuthApiError(error)
? authClientErrorMessage(error)
: "Authentication service is unavailable";
showToast(message);
}Extend the protocol
protocol can replace operations, remove a group with null, or add local functions with inferred TypeScript types. Local functions do not gain schema-backed wire encoding or response decoding. For a custom HTTP endpoint, define an Effect HttpApi, wrap it with defineAuthHttpApiExtension, and place it under protocol.extensions; it then shares the client's URL, fetch layer, request defaults, cancellation, and lifecycle.
Changing the browser protocol does not change the server. Mount the matching server endpoint explicitly. Continue with the Quick Start, HTTP Operations, or Password authentication.