# Add authentication

**Scope.** This recipe adds sign-in, sessions, and a `/me` identity endpoint to an existing NetScript workspace by installing the official **`auth`** plugin. You will choose an authentication backend, run the auth database migration, set the backend's environment, and verify a live session through the `auth-api` service on **`:8094`**. By the end you have a working OAuth/OIDC sign-in flow on the default backend (`kv-oauth`) and a clear picture of what the two non-interactive backends (`workos`, `better-auth`) do and do not provide.

This is the task-oriented companion to the [authentication capability hub](https://rickylabs.github.io/netscript/capabilities/auth/) (the headline API and endpoint map) and the [authentication model explanation](https://rickylabs.github.io/netscript/explanation/auth-model/) (why the backend is a pure adapter behind a port). If you want the *why*, read those; if you want the *how*, stay here.

> Aspire is the control plane — start it first
>
> The
>
> auth-api
>
> service and its database/KV dependencies run as resources in the Aspire graph (the database is Postgres, the recommended engine;
>
> mysql
>
> /
>
> mssql
>
> run as Aspire containers too, while
>
> sqlite
>
> is file-backed — pick one at scaffold with
>
> --db
>
> ). Bring orchestration up
>
> before
>
> you run any
>
> netscript db
>
> command or hit an auth endpoint: from the project root,
>
> cd aspire && aspire start
>
> (dashboard at
>
> https://localhost:18888
>
> ). DB commands require Aspire running first. See
>
> the Aspire explanation
>
> for the resource graph.

> Alpha package pins
>
> The CLI scaffold emits exact alpha specifiers such as
>
> jsr:@netscript/plugin-auth-core@0.0.1-beta.11
>
> . Add the plugin through
>
> netscript plugin install @netscript/plugin-auth
>
> so the workspace gets the matching auth dependency, generated glue, registry entries, and Aspire resources together.

## Before you start

You need an existing workspace with a database, because the `kv-oauth` and `better-auth` backends persist sessions and accounts. The `auth` plugin sets `requiresDb: true` and `requiresKv: true`, so both a database and KV (Redis) must be in the Aspire graph. The database is polyglot — Postgres is the recommended default, but `mysql`, `mssql`, or `sqlite` are first-class alternatives selected at scaffold time with `netscript init --db <engine>`. (Postgres/MySQL/SQL Server run as an Aspire container resource; SQLite is file-backed with no container.)

**Prerequisites**

| Name | Type | Description |
| --- | --- | --- |
| `Workspace` | `netscript init` | An existing project. If you have none, scaffold one first — see the tutorials. |
| `netscript CLI` | `on PATH` | Installed globally: deno install --global --allow-all --name netscript jsr:@netscript/cli. Confirm with netscript --help. |
| `Aspire` | `aspire start` | Postgres + Redis up via the AppHost before any db command or endpoint call (cd aspire && aspire start). |
| `OAuth credentials` | `client id / secret` | For the default kv-oauth backend you need a real OAuth/OIDC app (e.g. a Google client id + secret + redirect URI). Without provider env, signin/callback are non-functional stubs. |

Throughout, run commands from your workspace root.

## Step 1 — Add the `auth` plugin

The `auth` plugin is a first-class official plugin installed the same way as `workers`, `sagas`, `triggers`, and `streams`. Add it with `plugin install`:

```sh
netscript plugin install @netscript/plugin-auth
```

This installs the unified `@netscript/plugin-auth` dependency, emits the user-owned `auth/mod.ts` glue barrel, and registers it. The plugin package composes **one active backend** behind the `auth-api` oRPC service and contributes the Prisma schema (`auth.prisma`), service entry, and `/api/v1/auth/*` routes.

> One plugin, one active backend
>
> @netscript/plugin-auth
>
> is a thin composition layer. The real authentication logic lives in a
>
> backend adapter
>
> — one of
>
> @netscript/auth-kv-oauth
>
> ,
>
> @netscript/auth-workos
>
> , or
>
> @netscript/auth-better-auth
>
> . The plugin selects
>
> exactly one
>
> active backend at runtime; there is no multi-active routing or cross-backend account linking in v1. You pick the backend in Step 2.

## Step 2 — Choose a backend with the auth CLI

The active backend is selected by the `NETSCRIPT_AUTH_BACKEND` environment variable (or the `auth.backend` appsettings key). Three backends are valid; the default is **`kv-oauth`**.

**Auth backends — capability matrix (NETSCRIPT_AUTH_BACKEND)**

| Name | Type | Description |
| --- | --- | --- |
| `kv-oauth` | `interactive (default)` | Full OAuth/OIDC redirect flow. Real signin + callback, KV-backed sessions with refresh-on-read, signout. The only backend that implements InteractiveFlowPort. Package @netscript/auth-kv-oauth. |
| `workos` | `non-interactive` | WorkOS AuthKit sealed wos-session cookie. Validates an existing session; signin/callback return AUTH_PROVIDER_ERROR (no interactive flow). Package @netscript/auth-workos. |
| `better-auth` | `non-interactive` | better-auth over Prisma. Validates an existing session; signin/callback return AUTH_PROVIDER_ERROR. Package @netscript/auth-better-auth. |

> Only kv-oauth is interactive — choose accordingly
>
> The
>
> signin
>
> and
>
> callback
>
> endpoints require a backend that implements the optional
>
> InteractiveFlowPort
>
> .
>
> Only `kv-oauth` does.
>
> On
>
> workos
>
> and
>
> better-auth
>
> , calling
>
> POST /api/v1/auth/signin
>
> or
>
> POST /api/v1/auth/callback
>
> returns a typed
>
> AUTH_PROVIDER_ERROR
>
> (502) explaining the backend "does not expose an interactive flow." Those backends are for environments where sign-in already happened elsewhere and you only need NetScript to
>
> validate
>
> the session (
>
> session
>
> /
>
> me
>
> ). If you need NetScript to drive the login redirect, use
>
> kv-oauth
>
> .

For the rest of this recipe we use `kv-oauth`. Persist the choice in the workspace boot seam and confirm what the service will use:

```sh
netscript plugin auth backend set kv-oauth
netscript plugin auth backend show
netscript plugin doctor
```

The command reconciles `NETSCRIPT_AUTH_BACKEND` in the project `.env`, so scaffolded service tasks boot without a shell-specific export. Directly setting the environment variable or the `auth.backend` / `Auth.Backend` appsettings key remains an escape hatch for deployment systems that own configuration externally.

## Step 3 — Run the auth database migration

The `auth` plugin contributes a package-provided **`auth.prisma`** schema, which is aggregated into your project's database schema at `db generate` (Postgres is the recommended engine; or `mysql` / `mssql` / `sqlite` — the auth models persist through Prisma, so they follow whichever engine you scaffolded with `--db`). It defines four better-auth-shaped models mapped to these tables:

**auth.prisma models → your database tables**

| Name | Type | Description |
| --- | --- | --- |
| `User` | `auth_users` | Authenticated principals. Populated by backends that persist users (better-auth). |
| `Session` | `auth_sessions` | Server-side session records. kv-oauth keeps sessions in KV; this table backs the Prisma-persisting backend. |
| `Account` | `auth_accounts` | Linked provider accounts (the OAuth/OIDC identities behind a user). |
| `Verification` | `auth_verifications` | Verification / challenge records used during account flows. |

> Which backends actually use these tables
>
> auth.prisma
>
> is provisioned for every install so the schema is consistent, but storage differs by backend:
>
> kv-oauth
>
> stores sessions in Deno KV (not these tables),
>
> WorkOS
>
> is effectively stateless (sealed cookie), and
>
> better-auth
>
> is the backend that reads/writes
>
> auth_users
>
> /
>
> auth_sessions
>
> /
>
> auth_accounts
>
> /
>
> auth_verifications
>
> through Prisma. The migration runs regardless; it is the persistence path for the Prisma-backed backend.

With Aspire running (Step 0), generate and apply the migration the same way you do for any plugin schema:

```sh
netscript db init --name init    # first time only — create the migration
netscript db generate            # generate Prisma client + Zod schemas from the aggregated schema
netscript db seed                # optional seed data
netscript db status              # confirm the migration is applied
```

See [Run a database migration](https://rickylabs.github.io/netscript/data-persistence/how-to/database-migration/) for the full DB workflow and the Aspire-up dependency.

## Step 4 — Configure the provider and secrets

Each backend reads its own environment block. The auth CLI owns the normal setup path and writes the same project `.env` seam as Step 2. For GitHub on `kv-oauth`:

```sh
# Generate the boot key through the CLI and persist it with the provider config
KV_OAUTH_KEY="$(netscript plugin auth secret generate kv-oauth-key)"
netscript plugin auth provider set \
  --preset github \
  --client-id your-client-id \
  --client-secret your-client-secret \
  --redirect-uri http://localhost:8094/api/v1/auth/callback \
  --kv-oauth-key "$KV_OAUTH_KEY"
```

Tenant presets such as `okta`, `auth0`, `azure-ad`, `aws-cognito`, `logto`, and `clerk` additionally accept `--issuer`. The non-interactive variants use their boot-native credential names:

```sh
netscript plugin auth provider set --preset workos \
  --api-key sk_... --client-id client_... --cookie-password "$(netscript plugin auth secret generate workos-cookie)"

netscript plugin auth provider set --preset better-auth \
  --secret "$(netscript plugin auth secret generate better-auth)"
```

The explicit exports below are the escape hatch for CI/deployment systems that inject environment variables themselves; they are no longer required for ordinary workspace setup.

```sh
# Selects the interactive OAuth/OIDC backend
export NETSCRIPT_AUTH_BACKEND=kv-oauth

# Provider credentials (e.g. a Google OAuth app)
export NETSCRIPT_AUTH_CLIENT_ID=your-client-id
export NETSCRIPT_AUTH_CLIENT_SECRET=your-client-secret
export NETSCRIPT_AUTH_REDIRECT_URI=http://localhost:8094/api/v1/auth/callback

# OIDC discovery / endpoints (preset providers fill these for you)
export NETSCRIPT_AUTH_ISSUER=https://accounts.google.com
export NETSCRIPT_AUTH_AUTHORIZATION_ENDPOINT=https://accounts.google.com/o/oauth2/v2/auth
export NETSCRIPT_AUTH_TOKEN_ENDPOINT=https://oauth2.googleapis.com/token
export NETSCRIPT_AUTH_USERINFO_ENDPOINT=https://openidconnect.googleapis.com/v1/userinfo
export NETSCRIPT_AUTH_SCOPES=openid email profile

# Optional: cookie + KV tuning
export NETSCRIPT_AUTH_COOKIE_NAME=__Host-ns_session
export NETSCRIPT_AUTH_KV_OAUTH_KEY=<base64url-encoded-32-byte-secret>  # required for kv-oauth: missing key material is a startup error
# export NETSCRIPT_AUTH_ALLOW_INSECURE_REQUESTS=false

export PORT=8094
```

```sh
export NETSCRIPT_AUTH_BACKEND=workos

export WORKOS_API_KEY=sk_...
export WORKOS_CLIENT_ID=client_...
export WORKOS_COOKIE_PASSWORD=at-least-32-characters-of-entropy

# signin/callback return AUTH_PROVIDER_ERROR on this backend.
# It validates an existing WorkOS AuthKit session (session/me).
export PORT=8094
```

```sh
export NETSCRIPT_AUTH_BACKEND=better-auth

export BETTER_AUTH_SECRET=at-least-32-characters-of-entropy
export DB_PROVIDER=postgres

# Persists users/sessions/accounts via auth.prisma (Step 3).
# signin/callback return AUTH_PROVIDER_ERROR — validate-only.
export PORT=8094
```

> Provider presets fill the OIDC endpoints for you
>
> You rarely hand-type issuer/authorization/token/userinfo URLs. The
>
> kv-oauth
>
> package ships provider presets —
>
> github
>
> ,
>
> google
>
> ,
>
> gitlab
>
> ,
>
> discord
>
> ,
>
> slack
>
> ,
>
> spotify
>
> ,
>
> facebook
>
> ,
>
> twitter
>
> , plus tenant-based
>
> auth0
>
> ,
>
> okta
>
> ,
>
> awsCognito
>
> ,
>
> azureAd
>
> ,
>
> logto
>
> ,
>
> clerk
>
> — that encode the correct endpoints. Pass one to
>
> createKvOAuthBackend
>
> in Step 5, or call
>
> defineOAuthProvider(...)
>
> for a custom provider.

## Step 5 — The kv-oauth happy path (code)

When you compose the backend in code (for a custom service entry, a test, or a non-scaffold wiring), the interactive `kv-oauth` backend is one `await` call. Pass a provider preset from `providers.*`:

```ts
import { createKvOAuthBackend, providers } from "@netscript/auth-kv-oauth";

// providers.google(...) is a preset that fills the OIDC endpoints for you.
const backend = await createKvOAuthBackend({
  provider: providers.google({
    clientId: Deno.env.get("NETSCRIPT_AUTH_CLIENT_ID")!,
    clientSecret: Deno.env.get("NETSCRIPT_AUTH_CLIENT_SECRET")!,
    redirectUri: "http://localhost:8094/api/v1/auth/callback",
  }),
});

// backend implements AuthBackendPort AND the optional InteractiveFlowPort
// (signIn / handleCallback / getSessionId / signOut), so the auth-api
// signin + callback endpoints are live on this backend.
console.log(backend.name); // "kv-oauth"
```

```ts
import { createKvOAuthBackend, providers } from "@netscript/auth-kv-oauth";

// GitHub instead of Google — same shape, different preset.
const github = await createKvOAuthBackend({
  provider: providers.github({
    clientId: Deno.env.get("NETSCRIPT_AUTH_CLIENT_ID")!,
    clientSecret: Deno.env.get("NETSCRIPT_AUTH_CLIENT_SECRET")!,
    redirectUri: "http://localhost:8094/api/v1/auth/callback",
  }),
});

// Tenant providers (auth0, okta, azureAd, awsCognito, logto, clerk) take
// a tenant/domain in addition to the client credentials.
```

The returned `backend` satisfies the `AuthBackendPort` seam that `@netscript/plugin-auth-core` defines, and — because it is `kv-oauth` — also the optional `InteractiveFlowPort`. That is precisely what makes `signin` and `callback` work on this backend and fail loud on the other two. For the port architecture behind this, read [the authentication model](https://rickylabs.github.io/netscript/explanation/auth-model/).

## Step 6 — Start the service and the auth endpoints

With Aspire running, the `auth-api` service binds **port 8094** and mounts five endpoints under the public REST prefix **`/api/v1/auth/*`** (the oRPC surface is mirrored at `/api/rpc/v1/auth/*`):

**auth-api endpoints (:8094, /api/v1/auth/*)**

| Name | Type | Description |
| --- | --- | --- |
| `POST /api/v1/auth/signin` | `interactive only` | Begin the OAuth/OIDC redirect flow. Live on kv-oauth; returns AUTH_PROVIDER_ERROR on workos/better-auth. |
| `POST /api/v1/auth/callback` | `interactive only` | Complete the provider redirect, mint a session. Live on kv-oauth; AUTH_PROVIDER_ERROR on the others. |
| `POST /api/v1/auth/signout` | `session` | Revoke the current session and clear the session cookie. |
| `GET /api/v1/auth/session` | `session` | Return the current session if one is present and valid. Works on all backends. |
| `GET /api/v1/auth/me` | `identity` | Return the authenticated principal (the resolved user). Works on all backends. |

The service also exposes liveness/readiness probes at `/health/live` and `/health/ready`, plus OpenAPI docs, through the standard `@netscript/service` builder. Watch it come up in the Aspire dashboard at [https://localhost:18888](https://localhost:18888) under the `auth-api` resource.

## Step 7 — Verify a session

Confirm the service is up and the session endpoint responds. On a fresh, unauthenticated request, `session` reports no active session — which proves the endpoint is wired even before you complete a login:

```sh
# Service is alive
curl http://localhost:8094/health/ready

# No session yet — confirms the endpoint is mounted and reachable
curl http://localhost:8094/api/v1/auth/session
```

To exercise the full interactive flow on `kv-oauth`, drive the redirect from a browser: open `POST /api/v1/auth/signin` (the service issues the provider redirect), authenticate with your provider, let the provider call back to `/api/v1/auth/callback`, then re-check the session and identity with the cookie the flow set:

```sh
# After completing the browser sign-in, the session cookie is set.
# Re-checking now returns the active session and the resolved principal:
curl -b cookies.txt http://localhost:8094/api/v1/auth/session
curl -b cookies.txt http://localhost:8094/api/v1/auth/me
```

A successful `GET /api/v1/auth/session` after sign-in returns the active session; `GET /api/v1/auth/me` returns the authenticated principal. That round trip is the proof the backend is composed, the migration is applied, and the provider credentials are correct.

> Local smoke without real credentials
>
> If provider env is missing, the
>
> kv-oauth
>
> backend falls back to a non-functional local-default endpoint set so the service still boots and
>
> /health
>
> ,
>
> session
>
> , and
>
> me
>
> answer. The default is suitable for scaffold smoke tests. Real
>
> signin
>
> /
>
> callback
>
> require genuine provider credentials — the fallback is a stub path, not a working login.

## Production pitfalls

> Read before you ship authentication
>
> - **Wrong backend for the job** — `signin`/`callback` only work on `kv-oauth`. If those endpoints return `AUTH_PROVIDER_ERROR`, you are on `workos` or `better-auth`, which validate sessions but do not drive the login redirect. Set `NETSCRIPT_AUTH_BACKEND=kv-oauth` for an interactive flow.
> - **Single active backend** — there is exactly one backend at a time. No multi-active routing, cross-backend account linking, global logout, historical replay, or paged session mirror in v1. Plan your identity model around one provider path.
> - **No auth audit/telemetry surface yet** — there is **no** dedicated auth audit log or auth-specific telemetry API on main. Do not build dashboards against an auth audit stream that does not exist; the `defaultTelemetry` flag is generic OTLP plumbing, not an auth audit trail.
> - **Alpha package pins** — scaffolded `jsr:...` imports use exact `@0.0.1-beta.11` pins. Keep the generated workspace on one aligned NetScript version.
> - **Provider env is required for real login** — without `NETSCRIPT_AUTH_CLIENT_ID`/`SECRET`/`REDIRECT_URI` the `kv-oauth` backend boots into a stub fallback; `session`/`me` answer but no real sign-in is possible.
> - **Aspire down** — a 404 on `:8094` or a DB error during `netscript db` almost always means orchestration is not running. `cd aspire &&
>   aspire start` first.

## See also

[Authentication capability  The auth-api service, the five endpoints, and the three-backend capability matrix in one hub.](https://rickylabs.github.io/netscript/netscript/capabilities/auth/) [The authentication model  Why the backend is a pure adapter behind AuthBackendPort, and how the plugin composes one active backend.](https://rickylabs.github.io/netscript/netscript/explanation/auth-model/) [service reference  The @netscript/service builder that auth-api is built on — RPC mount, health, OpenAPI, service info.](https://rickylabs.github.io/netscript/netscript/reference/service/) [Run a database migration  The full db init / generate / seed / status workflow that applies auth.prisma.](https://rickylabs.github.io/netscript/netscript/data-persistence/how-to/database-migration/)
