# Auth

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

```bash
npx extraorbital add authlocker
```

```txt
✓ 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`](/docs/quickstart#how-autopilot-decides) without naming it.

## What it writes

| Variable | Example |
| --- | --- |
| `AUTHLOCKER_CLIENT_ID` | `al_2mJyR1oqIl9P2DJCYg8XEg` |
| `AUTHLOCKER_CLIENT_SECRET` | `als_9c4e…` |
| `AUTHLOCKER_ISSUER` | `https://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

```bash
npx extraorbital add authlocker [--slug <name>]
```

| Flag | Default | Description |
| --- | --- | --- |
| `--slug` | `default` | A 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](/pricing).

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:

```ts title="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:

```tsx title="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](https://authlocker.dev/components) generates both
the React and the server code for whatever arrangement you pick.

---

More for agents: [Docs index](https://extraorbital.dev/sitemap.md) · [llms.txt](https://extraorbital.dev/llms.txt) · [agents.md](https://extraorbital.dev/agents.md)
