Skip to main content
Alpha

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 (the headline API and endpoint map) and the authentication model explanation (why the backend is a pure adapter behind a port). If you want the why, read those; if you want the how, stay here.

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
NameTypeDescription
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 add:

netscript plugin add @netscript/plugin-auth

This scaffolds the unified @netscript/plugin-auth plugin into your workspace and registers it. The plugin composes one active backend behind the auth-api oRPC service and contributes a Prisma schema (auth.prisma), a service entry (services/src/main.ts), and the /api/v1/auth/* routes.

Step 2 — Choose a backend with NETSCRIPT_AUTH_BACKEND

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)
NameTypeDescription
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.

For the rest of this recipe we use the default, kv-oauth. You can make the choice explicit in your environment:

export NETSCRIPT_AUTH_BACKEND=kv-oauth

Step 3 — Run the auth database migration

The auth plugin contributes a Prisma schema, plugins/auth/database/auth.prisma, which is aggregated into your project's database schema at db generate (Postgres by default; 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
NameTypeDescription
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.

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

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 for the full DB workflow and the Aspire-up dependency.

Step 4 — Configure the backend environment

Each backend reads its own environment block. For the default kv-oauth backend you supply a real OAuth/OIDC provider (client id, secret, redirect URI) plus optional cookie/KV tuning. Set these in your appsettings.json / environment before starting the service.

# 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
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
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

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.*:

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"
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.

Step 6 — Start the service and the auth endpoints

With aspire startning, 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/*)
NameTypeDescription
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 http://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:

# 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:

# 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.

Production pitfalls

See also