Auth

Sign-in for your app — social providers and one-time codes — in one command.

AI/LLM: this page is available in plain markdown at /docs/resources/authlocker.md

npx extraorbital add authlocker
✓ Provisioned authlocker/default
✓ Wrote 3 variables to .env

  AUTHLOCKER_CLIENT_ID, AUTHLOCKER_CLIENT_SECRET, AUTHLOCKER_ISSUER

A project that mentions AUTHLOCKER_CLIENT_ID gets this from extraorbital provision without naming it.

What it writes

VariableExample
AUTHLOCKER_CLIENT_IDal_2mJyR1oqIl9P2DJCYg8XEg
AUTHLOCKER_CLIENT_SECRETals_9c4e…
AUTHLOCKER_ISSUERhttps://authlocker.dev

Two ways in, from one credential pair. Social sign-in is a standards-compliant OAuth 2.1 / OIDC flow, so any OIDC library speaks it from the discovery document and no vendor SDK is required. Email and SMS one-time codes are a pair of server-to-server calls — the service generates every code, counts every attempt and owns the lockout, so none of that lives in your app.

You do not register a callback URL. The first one your app actually uses is pinned to the client, per provider, and every later request must match it exactly. Localhost always works and pins nothing, so development never spends the pin on a URL no user will visit.

Options

npx extraorbital add authlocker [--slug <name>]
FlagDefaultDescription
--slugdefaultA second, fully separate client

One client per app, not per environment. The subject in the token is pairwise — derived per client — so two clients see the same person as two different users.

Prototype allowance

Fair use on the Prototype plan. Social sign-in and email codes are included; SMS draws on a shared daily ceiling, which is enough to build against and deliberately not enough to be worth abusing. Past it, SMS is passed through at cost — from $0.011 a message, varying by destination — on a paid plan.

Usage is metered per login, per code sent and per code approved, so extraorbital ledger shows what was actually used rather than a flat zero.

Use it

One route handler holds the secret, because the verify API takes a client secret and answers no CORS headers — a browser can never call it:

app/api/auth/verify-user-identity/[...path]/route.ts
import { createAuthLockerHandler } from "@authlocker/next";

export const { GET, POST } = createAuthLockerHandler({
  capabilities: ["socials.google", "socials.github", "otp.email"],
  async onUserVerified(request, user) {
    // user: { method, provider, sub, email, emailVerified, name, picture }
    // Write your session cookie here — this response is the one that carries it.
  },
});

And the client half needs no configuration, because both halves read the same path constant:

app/layout.tsx
import { AuthLockerNextProvider } from "@authlocker/next/client";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return <AuthLockerNextProvider>{children}</AuthLockerNextProvider>;
}

Prebuilt sign-in components install with npx shadcn@latest add, and authlocker.dev/components generates both the React and the server code for whatever arrangement you pick.