Local auth provider

@sometic/auth-local adapts a JSON REST backend to the Sometic AuthProvider contract. No Firebase or Supabase SDK. You own the HTTP shapes; optional mappers normalize responses.

When to use

  • First-party API with email/password (and optional register / refresh / reset)
  • Full control over token format and session endpoints
  • Apps that must avoid cloud auth SDKs

When not to use

  • You already standardize on Firebase / Supabase / OIDC IdP (use those adapters)
  • You need browser OAuth against a third-party IdP (use OIDC or Supabase OAuth)

Installation

pnpm
pnpm add @sometic/auth @sometic/auth-local
npm
npm install @sometic/auth @sometic/auth-local
yarn
yarn add @sometic/auth @sometic/auth-local
bun
bun add @sometic/auth @sometic/auth-local

No optional peer SDK.

Usage

js
import { createAuth, createSessionStorageAuthStorage } from "@sometic/auth";
import { createLocalAuthProvider } from "@sometic/auth-local";

const provider = createLocalAuthProvider({
    baseUrl: "https://api.example.com",
    endpoints: {
        signIn: "/auth/sign-in",
        register: "/auth/register",
        refresh: "/auth/refresh",
        signOut: "/auth/sign-out",
        session: "/auth/session",
        passwordReset: "/auth/password-reset",
    },
});

const auth = createAuth({
    provider,
    storage: createSessionStorageAuthStorage(),
    autoRefresh: true,
});

await auth.signIn({ email: "user@example.com", password: "secret" });
ts
import { createAuth, createSessionStorageAuthStorage } from "@sometic/auth";
import { createLocalAuthProvider } from "@sometic/auth-local";
import type { AuthController } from "@sometic/auth";

const provider = createLocalAuthProvider({
    baseUrl: "https://api.example.com",
    endpoints: {
        signIn: "/auth/sign-in",
        register: "/auth/register",
        refresh: "/auth/refresh",
        signOut: "/auth/sign-out",
        session: "/auth/session",
        passwordReset: "/auth/password-reset",
    },
});

const auth: AuthController = createAuth({
    provider,
    storage: createSessionStorageAuthStorage(),
    autoRefresh: true,
});

await auth.signIn({ email: "user@example.com", password: "secret" });
js
import { createAuth, createSessionStorageAuthStorage } from "@sometic/auth";
import { createLocalAuthProvider } from "@sometic/auth-local";

const provider = createLocalAuthProvider({
    baseUrl: "https://api.example.com",
    endpoints: {
        signIn: "/auth/sign-in",
        register: "/auth/register",
        refresh: "/auth/refresh",
        signOut: "/auth/sign-out",
        session: "/auth/session",
        passwordReset: "/auth/password-reset",
    },
});

const auth = createAuth({
    provider,
    storage: createSessionStorageAuthStorage(),
    autoRefresh: true,
});

await auth.signIn({ email: "user@example.com", password: "secret" });

Options

ts
type LocalAuthProviderOptions = {
    baseUrl: string;
    endpoints?: {
        signIn?: string;
        register?: string;
        refresh?: string;
        signOut?: string;
        session?: string;
        passwordReset?: string;
    };
    fetcher?: typeof fetch;
    mapUser?: (payload: unknown) => AuthUser;
    mapTokens?: (payload: unknown) => AuthTokens | null;
    mapSession?: (payload: unknown) => AuthSession;
};

Defaults under baseUrl:

KeyDefault path
signIn/auth/sign-in
register/auth/register
refresh/auth/refresh
signOut/auth/sign-out
session/auth/session
passwordReset/auth/password-reset

Expected JSON (defaults)

Default mappers accept either nested or flat shapes:

json
{
    "user": { "id": "u1", "email": "a@b.c", "roles": ["admin"] },
    "tokens": {
        "accessToken": "…",
        "refreshToken": "…",
        "expiresIn": 3600,
        "tokenType": "Bearer"
    }
}

Also accepted: access_token, refresh_token, expires_in, token_type, and user.sub as id. Missing user id throws AUTH_INVALID_SESSION.

Capabilities

CapabilitySupported
signIn / signOut / register
getSession / refresh / getUser
passwordReset
emailVerification / oauth / mfano (extend your API + custom provider if needed)

Patterns

Custom mapper

ts
createLocalAuthProvider({
    baseUrl: "https://api.example.com",
    mapUser: (payload) => {
        const row = payload as { account: { uuid: string; mail: string } };
        return { id: row.account.uuid, email: row.account.mail };
    },
});

Inject fetcher (tests / cookies)

ts
createLocalAuthProvider({
    baseUrl: "https://api.example.com",
    fetcher: (input, init) => fetch(input, { ...init, credentials: "include" }),
});

Limitations

  • Assumes JSON REST; GraphQL needs a custom provider or mapper that posts GraphQL.
  • No OAuth/MFA unless you extend endpoints and wrap a custom AuthProvider.
  • Client adapter does not secure APIs; your backend enforces authz.

FAQ

Can I point endpoints at absolute URLs?

Yes. Paths that look like absolute URLs are used as-is.

How do I test without a server?

Prefer createTestAuthProvider for unit tests. For contract tests, inject a createMockFetcher from @sometic/http or a stub fetcher.