Skip to content

Start

Getting started

Define a shared contract, bind it to authentication strategies, and supply your application’s services with Layers. The same contract gives your client typed Effects and atoms.

bun add @yielded/auth@beta effect

For React, also install @effect/atom-react. Add companion packages for the storage and protocol adapters you choose.

packages/domain/auth-contract.ts
import { Schema } from "effect";
import { AuthContract } from "@yielded/auth";
export const AuthApi = AuthContract.make("app/Auth", {
claims: Schema.Struct({ displayName: Schema.String }),
actions: (sessions) => ({ signIn: AuthContract.passwordSignIn(sessions) }),
});

claims is the data each session carries. actions lists what clients may call; here, password sign-in. Every contract also includes getSession, requireSession, signOut, and renewSession. Keep this module free of server configuration, keys, and persistence so the browser can import it.

apps/server/auth.ts
import { Auth, Http, Password, Sessions } from "@yielded/auth";
import { AuthApi } from "@app/domain/auth-contract";
export const AppAuth = Auth.make(AuthApi, {
sessions: Sessions.stateful({ maxAge: "8 hours", idleTimeout: "30 minutes" }),
strategies: { password: Password.make() },
defaultStrategy: "password",
});
export const AuthRoutes = Http.layer(AppAuth, { origin: "https://app.example.com" });

Auth.make declares a yieldable service; constructing it performs no I/O. Http.layer serves the contract’s actions as routes and owns cookies, Origin, and CSRF checks.

The type of AuthRoutes lists every service your methods still need. The library supplies Web Crypto and empty lifecycle hooks; you supply the rest:

You supply With
Storage for credentials, sessions, and proofs Managed tables, your schema, or your services
Account checks and session claims Your account Layers; see passwords
Password hashing @yielded/auth-crypto/Password
Proof and request-binding keys Your secrets; see Layer wiring

Provide them to AuthRoutes and merge it with your router:

apps/server/routes.ts
import { Layer } from "effect";
import { ApplicationRoutes } from "./application-routes";
import { AuthRoutes } from "./auth";
import { AuthDependencies } from "./auth-live"; // storage, accounts, hashing, keys
export const Routes = Layer.mergeAll(AuthRoutes, ApplicationRoutes).pipe(
Layer.provide(AuthDependencies),
);

The managed Drizzle app is a complete composition: schema.ts maps a customer table, live.ts supplies accounts and storage, and server.ts serves the routes.

Inside an Effect route covered by the auth middleware, call the service with your validated input:

const auth = yield* AppAuth;
const result = yield* auth.signIn({ email, password });

The request boundary supplies credentials and delivers the session cookie. An Authenticated result carries the typed session; an additional-factor result must be completed before you grant access. Failures stay typed in the error channel; see rejected sign-ins.

apps/web/auth-client.ts
import { Atom as AuthAtom, Client } from "@yielded/auth";
import { AuthApi } from "@app/domain/auth-contract";
export const AppClient = Client.make(AuthApi, { baseUrl: "https://app.example.com" });
export const auth = AuthAtom.make(AppClient);

Use auth.session, auth.signIn, and auth.signOut directly as queries and mutations with ordinary @effect/atom-react hooks. Compose your own queries through auth.runtime to share the client and account lifetime:

apps/web/member-name.ts
import { Effect } from "effect";
import { AppClient, auth } from "./auth-client";
export const memberName = auth.runtime.atom(
Effect.gen(function* () {
const client = yield* AppClient;
const session = yield* client.auth.getSession();
return session?.claims.displayName ?? null;
}),
);

Fetch is configured by default. To customize transport, compose AppClient.layer with your HttpClient Layer and pass { layer: ClientLive } to AuthAtom.make. The Effect Atom client guide shows React, shared invalidation, and standalone Effect calls.

Then add an authentication method, such as passkeys or GitHub sign-in. Each method adds its required services to the server; its public actions belong in the shared contract.