W WRNexusJS
Preview guide · 0.8.7

Auth and permissions dashboard demo

This walkthrough builds a small team portal. Public visitors can read the home and pricing pages, users can sign in, and the dashboard separates ordinary members from administrators. Every authorization decision remains on the server.

1. Create the application

bunx @wrnexus/cli@0.8.7 create team-portal
cd team-portal
bun install
bun add @wrnexus/auth @wrnexus/authz @wrnexus/db @wrnexus/validation @wrnexus/ui
bunx wrnexus generate page pricing
bunx wrnexus generate page login
bunx wrnexus generate page dashboard
bunx wrnexus generate api session/login
bunx wrnexus generate api session/logout
bunx wrnexus generate schema login
bunx wrnexus authz init --dialect=sqlite
bunx wrnexus db new initial_auth --from-models
bunx wrnexus db migrate
bunx wrnexus generate types .

Expected success output includes created file paths, an initialized authorization catalog, the applied migration name, and the generated application declaration path.

2. Configure profiles and security

// wrnexus.config.ts
import type { AppConfig } from "@wrnexus/styles";

const config: AppConfig = {
  seo: { title: "Team Portal", titleTemplate: "%s | Team Portal" },
  theme: { default: "system", palette: "violet" },
  security: {
    contentSecurityPolicy: true,
    csrf: true,
    frameOptions: "deny",
  },
  profiles: {
    development: { envFiles: [".env", ".env.development"] },
    production: { envFiles: [".env", ".env.production"] },
  },
};

export default config;
# .env.example — commit names, never real secrets
DATABASE_URL=sqlite:./data/team-portal.db
SESSION_SECRET=replace-with-at-least-32-random-bytes
APP_ORIGIN=http://localhost:3000

Run wrnexus config . --explain --profile=production before deployment and confirm no development fallback or secret value is printed.

3. Create public pages and layout

// app/layouts/public.wrn
layout Public {
  view {
    <Navbar brand="Team Portal" />
    <main><slot /></main>
    <Footer copyright="Team Portal" />
  }
}

// app/pages/index.wrn
page Home {
  layout = "public"
  seo { title = "Home" description = "A secure portal for modern teams." }
  view {
    <Hero eyebrow="Team operations" title="One secure place for every team." />
    <FeatureGrid columns="3"><slot /></FeatureGrid>
  }
}

Create pricing.wrn, privacy.wrn, and terms.wrn with the same public layout. Public routes must not load private account data.

4. Define login validation and handlers

// app/schemas/login.ts
import { v } from "@wrnexus/validation";
export default v.object({
  email: v.string().trim().email(),
  password: v.string().min(12).max(128),
});

// app/api/session/login.ts
import schema from "../../schemas/login";
import { parseBody } from "@wrnexus/validation";

export const POST = async (ctx) => {
  const parsed = await parseBody(schema, ctx.req);
  if (!parsed.ok) return parsed.response;
  // Look up the account, verify its password, rotate the session,
  // and return the same failure shape for unknown users and bad passwords.
  return Response.json({ ok: true, redirect: "/dashboard" });
};

Use the exact installed authentication package API for account lookup, password verification, session rotation, rate limiting, and audit events. Do not copy placeholder authentication logic into production.

5. Declare roles and permissions

// app/authz/main.ts
export const permissions = [
  "dashboard:read",
  "member:read",
  "member:invite",
  "member:manage",
] as const;

export const roles = {
  member: ["dashboard:read", "member:read"],
  manager: ["dashboard:read", "member:read", "member:invite"],
  admin: ["dashboard:read", "member:read", "member:invite", "member:manage"],
} as const;
bunx wrnexus authz generate
bunx wrnexus authz list
bunx wrnexus contracts snapshot .

Expected output lists the four permission identifiers and generated authorization artifacts. Commit the contract snapshot so later permission drift is reviewable.

6. Protect the dashboard on the server

// app/middleware/auth.ts
export default async function requireUser(ctx, next) {
  const user = await readAuthenticatedUser(ctx);
  if (!user) return Response.redirect(new URL("/login", ctx.url), 303);
  ctx.state.user = user;
  return next();
}

// app/api/members.ts
export const GET = async (ctx) => {
  await requirePermission(ctx, "member:read");
  return Response.json({ members: await listMembers(ctx.state.user.tenantId) });
};

export const POST = async (ctx) => {
  await requirePermission(ctx, "member:invite");
  // Validate input and keep the tenant identifier server-owned.
  return Response.json({ ok: true }, { status: 201 });
};

Route middleware establishes identity; each API mutation still checks its exact permission and resource boundary. Hiding an Invite button is useful UX but never authorization.

7. Render the dashboard

page Dashboard {
  layout = "dashboard"
  ssr {
    api summary GET /api/dashboard { return summary }
    api members GET /api/members { return members }
  }
  view {
    <PageHeader eyebrow="Workspace" title="Dashboard" />
    <MetricGrid columns="3">
      <MetricCard label="Members" value={summary.memberCount} />
      <MetricCard label="Invitations" value={summary.invitationCount} />
      <MetricCard label="Active today" value={summary.activeToday} />
    </MetricGrid>
    <DataTable rows={members} />
  }
}

Keep dashboard data tenant-scoped in the API. Server rendering prevents an empty shell, while the client receives only the modules required for interactive controls.

8. Test denial paths and production

bunx wrnexus typecheck .
bunx wrnexus test unit .
bunx wrnexus test api .
bunx wrnexus test browser .
bunx wrnexus security audit .
bunx wrnexus contracts check .
bunx wrnexus build .
bunx wrnexus preview . --port=3000

Tests should prove anonymous dashboard access redirects, members cannot invite, managers can invite but cannot manage roles, administrators can manage roles, cross-tenant identifiers are rejected, login failures are rate-limited, CSRF failures return 403, and the production server starts from dist/server.js.

9. Demo checklist

  • Public home, pricing, privacy, and terms pages render without authentication.
  • Login creates and rotates a secure session.
  • Dashboard navigation changes by permission, while APIs enforce every permission independently.
  • Member lists and mutations are tenant-scoped on the server.
  • Development and production profiles resolve explicitly.
  • Typecheck, API tests, browser tests, security audit, contract check, build, and preview all pass.

Configuration

Keep configuration in wrnexus.config.ts, select an explicit profile, and store secrets only in validated environment variables. Use wrnexus config . --explain to review the resolved non-secret configuration.

Implementation workflow

bunx wrnexus doctor .
bunx wrnexus typecheck .
bunx wrnexus inspect routes .
bunx wrnexus build .

Start from the exact installed package page, implement the smallest server-owned contract, and add browser behavior only where interaction requires it. Run the production build because development-only success does not prove deployability.

Verification checklist

  • Inputs are validated at the authoritative server boundary.
  • Authentication and resource authorization are tested independently.
  • Generated routes and application types are current.
  • Error, empty, loading, denied, and success states are documented.
  • The production artifact starts and serves the expected route.

Release scope

This guide describes installed 0.8.7 capabilities. Follow linked package declarations for exact signatures; undocumented behavior is not guaranteed.

Browse package APIs · CLI reference · Troubleshooting · Support