Add auth and a session
You have a running workspace, but anyone can hit it. This chapter gives it an identity layer: you add
the official auth plugin, choose an authentication backend, run its database migration, and
verify a live session through the auth-api service on :8094. The lesson underneath the steps:
auth in NetScript is a pluggable backend plus a session — you pick the backend with one
environment variable, and the contract is identical no matter which one you pick.
What you will build
A working sign-in surface for my-workspace/: the auth plugin installed, the interactive
kv-oauth backend selected, the auth.prisma migration applied, and the auth-api service answering
on :8094. By the end you can hit GET /api/v1/auth/session and GET /api/v1/auth/me and watch the
service correctly report "no session yet" before login — proof the backend is composed and the session
endpoints are wired.
Before you begin
You need the workspace from chapter 1 with aspire startning —
the auth plugin contributes a service and a Prisma schema that Aspire and netscript db reach through
the running graph. Confirm the base is up:
# In my-workspace/, with `aspire start` up in another terminal
curl http://localhost:3001/health # the workspace service from chapter 1
Step 1 — Add the auth plugin
The auth plugin is a first-class official plugin, installed the same way as workers, sagas, and
triggers. From the workspace root:
netscript plugin add @netscript/plugin-auth
This scaffolds the unified @netscript/plugin-auth plugin into plugins/, registers it, and
contributes three things to your workspace: a Prisma schema (auth.prisma), a service entry that
becomes the auth-api service, and the /api/v1/auth/* routes. Confirm it landed:
netscript plugin list
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, the only one
that drives an interactive sign-in.
| 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. 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. |
A team workspace needs users to log in, so this track uses the interactive default. 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, that
aggregates into your primary Postgres at db generate. With aspire startning, run the standard database
loop from the workspace root:
netscript db init --name init # first time only — create the migration
netscript db generate # generate the Prisma client + Zod schemas
netscript db seed # optional seed data
netscript db status # confirm the migration is applied
Step 4 — Configure the kv-oauth backend
The kv-oauth backend needs a real OAuth/OIDC provider (a client id, secret, and redirect URI) plus a
key for the session token at rest. Set these in your environment before starting the service:
# Selects the interactive backend
export NETSCRIPT_AUTH_BACKEND=kv-oauth
# Provider credentials (e.g. a Google or GitHub 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
# Required for kv-oauth: missing key material is a startup error
export NETSCRIPT_AUTH_KV_OAUTH_KEY=<base64url-encoded-32-byte-secret>
export PORT=8094
Step 5 — Compose the backend in code
When you wire the backend yourself — for a custom service entry or a test — the interactive kv-oauth
backend is one await call. You pass a provider preset from providers.*, and you get back something
that satisfies the AuthBackendPort seam that @netscript/plugin-auth-core defines (and, because it
is kv-oauth, the optional InteractiveFlowPort too):
// services/auth/src/backend.ts
import { createKvOAuthBackend, getRequiredEnv, providers } from '@netscript/auth-kv-oauth';
// providers.google(...) is a preset that fills the OIDC endpoints for you.
export const backend = await createKvOAuthBackend({
provider: providers.google({
clientId: getRequiredEnv('NETSCRIPT_AUTH_CLIENT_ID'),
clientSecret: getRequiredEnv('NETSCRIPT_AUTH_CLIENT_SECRET'),
redirectUri: getRequiredEnv('NETSCRIPT_AUTH_REDIRECT_URI'),
}),
});
// backend implements AuthBackendPort AND the optional InteractiveFlowPort,
// so the auth-api signin + callback endpoints are live on this backend.
console.log(backend.name); // 'kv-oauth'
// Same factory, but define the OIDC provider yourself when your IdP is
// not one of the built-in presets.
import { createKvOAuthBackend, defineOAuthProvider, getRequiredEnv } from '@netscript/auth-kv-oauth';
const provider = defineOAuthProvider({
id: 'my-idp',
clientId: getRequiredEnv('NETSCRIPT_AUTH_CLIENT_ID'),
clientSecret: getRequiredEnv('NETSCRIPT_AUTH_CLIENT_SECRET'),
authorizationEndpoint: getRequiredEnv('NETSCRIPT_AUTH_AUTHORIZATION_ENDPOINT'),
tokenEndpoint: getRequiredEnv('NETSCRIPT_AUTH_TOKEN_ENDPOINT'),
userInfoEndpoint: getRequiredEnv('NETSCRIPT_AUTH_USERINFO_ENDPOINT'),
redirectUri: getRequiredEnv('NETSCRIPT_AUTH_REDIRECT_URI'),
scopes: ['openid', 'profile', 'email'],
});
export const backend = await createKvOAuthBackend({ provider });
The core seam is small and contract-only. @netscript/plugin-auth-core defines the
AuthBackendPort and the registry helpers that resolve one backend from many — these are the types
the auth plugin composes against, confirmed on the package's public surface:
| Name | Type | Description |
|---|---|---|
AuthBackendPort |
type |
The contract every backend implements — the auth-api surface is identical across all three backends because they all satisfy this port. |
createAuthBackendRegistry / resolveBackend |
function |
Build a registry of named backends and resolve the single active one (DEFAULT_AUTH_BACKEND_NAME is the fallback). |
AuthSession |
type |
The normalized session the store persists — id, subject, state, scopes, claims, issuedAt / expiresAt. |
createHmacSessionTokenCrypto |
function |
HMAC-signs the opaque session token so the cookie value cannot be forged. |
authContractV1 |
contract |
The five-route auth contract: signin, signout, callback, session, me. |
Step 6 — Verify a session
With aspire startning, the auth-api service binds port 8094 and mounts the five auth endpoints
under /api/v1/auth/*. 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
# Identity endpoint: { "authenticated": false } (HTTP 200) before login
curl http://localhost:8094/api/v1/auth/me
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 it call back to /api/v1/auth/callback, then re-check the session with the cookie the
flow set:
# After completing the browser sign-in, the __Host-ns_session cookie is set.
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/me after sign-in returns { authenticated: true, user, session }.
That round trip is the proof the backend is composed, the migration is applied, and the provider
credentials are correct.
- [ ]
netscript plugin listshows theauthplugin. - [ ]
netscript db statusreports theauth.prismamigration applied. - [ ]
NETSCRIPT_AUTH_BACKEND=kv-oauthand the provider env vars are set. - [ ]
curl http://localhost:8094/health/readysucceeds. - [ ]
curl http://localhost:8094/api/v1/auth/mereturns{ "authenticated": false }before login.
What you built
Your workspace now has an identity layer: the auth plugin composed onto the interactive kv-oauth
backend, the auth.prisma migration applied, and the auth-api service answering on :8094 with the
five-route contract. You proved the session endpoints are wired. Next you give the workspace its own
data to protect — a separate database for workspace records.