Skip to content
Back to skills

Auth Package

ASecurity

This skill should be used when the user asks to "add authentication", "protect a route", "use auth hooks", "integrate Auth0", "add login/logout", "use AuthProvider", "verify JWT", or mentions @mbe/auth, OIDC, access tokens, or authentication in React or Fastify.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 12, 2026
testingtypescriptgobashreactnodetestingapifrontendbackendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add aibot88/sec_skill_store --skill auth-package --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Auth Package?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Auth Package
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aibot88-auth-package/badge)](https://www.skillsdirectory.com/skills/aibot88-auth-package)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: auth-package
description: This skill should be used when the user asks to "add authentication", "protect a route", "use auth hooks", "integrate Auth0", "add login/logout", "use AuthProvider", "verify JWT", or mentions @mbe/auth, OIDC, access tokens, or authentication in React or Fastify.
---

# Auth Package Development Skill

This skill provides patterns for using the `@mbe/auth` package, a portable OIDC-compliant authentication layer with React hooks for frontend and a Fastify plugin for backend JWT validation.

## Package Overview

**Location**: `packages/auth/`
**Package**: `@mbe/auth`
**Auth Provider**: Auth0 (OIDC-compliant)

### Module Entry Points

| Import | Purpose |
|--------|---------|
| `@mbe/auth` | All exports (React + Fastify + types) |
| `@mbe/auth/react` | React hooks and AuthProvider only |
| `@mbe/auth/fastify` | Fastify plugin only |
| `@mbe/auth/types` | Type definitions only |

## React Authentication

### Setting Up AuthProvider

Wrap the app with `AuthProvider` in the root component:

```typescript
import { AuthProvider } from "@mbe/auth/react";
import type { OIDCConfig } from "@mbe/auth/types";

const config: OIDCConfig = {
  authority: import.meta.env.VITE_AUTH_AUTHORITY,
  clientId: import.meta.env.VITE_AUTH_CLIENT_ID,
  redirectUri: import.meta.env.VITE_AUTH_REDIRECT_URI,
  audience: import.meta.env.VITE_AUTH_AUDIENCE,
  scope: "openid profile email",  // default
};

function App() {
  return (
    <AuthProvider config={config}>
      <RouterProvider router={router} />
    </AuthProvider>
  );
}
```

### useAuth Hook

Primary hook for authentication state and actions:

```typescript
import { useAuth } from "@mbe/auth/react";

function MyComponent() {
  const {
    isLoading,        // true while initializing auth state
    isAuthenticated,  // true if user has valid session
    user,             // AuthUser | null
    accessToken,      // string | null (for API calls)
    signIn,           // () => void - redirect to login
    signOut,          // () => void - redirect to logout
    signInSilent,     // () => Promise - refresh token
    error,            // Error | null
  } = useAuth();

  if (isLoading) return <Spinner />;
  if (!isAuthenticated) return <LoginPrompt onLogin={signIn} />;

  return <div>Welcome, {user?.name}</div>;
}
```

### useAccessToken Hook

Convenience hook for getting the access token:

```typescript
import { useAccessToken } from "@mbe/auth/react";

function ApiComponent() {
  const accessToken = useAccessToken();

  const fetchData = async () => {
    const res = await fetch("/api/v1/users/me", {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });
    return res.json();
  };
}
```

### useRequireAuth Hook

Auto-triggers login if not authenticated:

```typescript
import { useRequireAuth } from "@mbe/auth/react";

function ProtectedPage() {
  const { isLoading, user } = useRequireAuth();

  if (isLoading) return <Spinner />;
  // User is guaranteed authenticated here
  return <Dashboard user={user} />;
}
```

### Logout Pattern

```typescript
function Header() {
  const { user, signOut } = useAuth();

  return (
    <header>
      <span>{user?.name}</span>
      <button onClick={signOut}>Logout</button>
    </header>
  );
}
```

## Fastify Authentication

### Using the Auth Plugin

Register the plugin to protect all routes:

```typescript
import Fastify from "fastify";
import { authPlugin, getAuthPluginOptionsFromEnv } from "@mbe/auth/fastify";

const app = Fastify();

// Load config from AUTH_AUTHORITY and AUTH_AUDIENCE env vars
const authOptions = getAuthPluginOptionsFromEnv();

await app.register(authPlugin, {
  ...authOptions,
  excludePaths: ["/health", "/docs"],  // Skip auth for these
});

// All routes now have request.user populated (if valid JWT)
```

### Manual Plugin Configuration

```typescript
import { authPlugin } from "@mbe/auth/fastify";

await app.register(authPlugin, {
  authority: "https://dev-ytbgmz5ls3wh4xdx.us.auth0.com",
  audience: "https://api.mattbutlerengineering.com",
  excludePaths: ["/health"],
});
```

### Protecting Individual Routes

Use `requireAuth` as a preHandler:

```typescript
import { requireAuth } from "@mbe/auth/fastify";

fastify.get(
  "/me",
  {
    preHandler: requireAuth,
    schema: {
      security: [{ bearerAuth: [] }],
      // ...
    },
  },
  async (request, reply) => {
    // request.user is guaranteed to exist
    const { id, email, name } = request.user!;
    return { data: { id, email, name } };
  }
);
```

### Accessing the Authenticated User

After auth validation, `request.user` contains:

```typescript
interface AuthUser {
  id: string;           // User ID (from JWT sub claim)
  email?: string;
  name?: string;
  picture?: string;
  emailVerified?: boolean;
  raw: JWTPayload;      // Full decoded token
}

// Usage in route handler
async (request, reply) => {
  const user = request.user;
  if (!user) {
    return reply.code(401).send({ error: "Unauthorized" });
  }
  // Use user.id, user.email, etc.
}
```

## Type Definitions

### OIDCConfig

```typescript
interface OIDCConfig {
  authority: string;              // OIDC provider URL
  clientId: string;               // OAuth client ID
  redirectUri: string;            // Post-login redirect
  postLogoutRedirectUri?: string; // Post-logout redirect
  scope?: string;                 // Default: "openid profile email"
  audience?: string;              // API audience
}
```

### JWTPayload

```typescript
interface JWTPayload {
  sub: string;              // Subject (user ID)
  iss: string;              // Issuer
  aud: string | string[];   // Audience
  exp: number;              // Expiration timestamp
  iat: number;              // Issued at timestamp
  email?: string;
  email_verified?: boolean;
  name?: string;
  picture?: string;
  [key: string]: unknown;   // Custom claims
}
```

## Environment Variables

### Frontend (Vite)

```bash
VITE_AUTH_AUTHORITY=https://dev-ytbgmz5ls3wh4xdx.us.auth0.com
VITE_AUTH_CLIENT_ID=<Auth0 App Client ID>
VITE_AUTH_REDIRECT_URI=http://localhost:3002/hospitality/callback
VITE_AUTH_AUDIENCE=https://api.mattbutlerengineering.com
```

### Backend (Node.js)

```bash
AUTH_AUTHORITY=https://dev-ytbgmz5ls3wh4xdx.us.auth0.com
AUTH_AUDIENCE=https://api.mattbutlerengineering.com
```

## Auth0 Configuration

- **Domain**: `dev-ytbgmz5ls3wh4xdx.us.auth0.com`
- **API Identifier**: `https://api.mattbutlerengineering.com`
- **Management**: Pulumi IaC in `infrastructure/pulumi/`

## Common Patterns

### Protected Route with Loading State

```typescript
function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isLoading, isAuthenticated, signIn } = useAuth();

  if (isLoading) return <LoadingSpinner />;
  if (!isAuthenticated) {
    signIn();
    return <div>Redirecting to login...</div>;
  }
  return <>{children}</>;
}
```

### API Client with Auth Token

```typescript
import { useAccessToken } from "@mbe/auth/react";

function useApiClient() {
  const accessToken = useAccessToken();

  return {
    get: async (path: string) => {
      const res = await fetch(`/api${path}`, {
        headers: accessToken
          ? { Authorization: `Bearer ${accessToken}` }
          : {},
      });
      return res.json();
    },
  };
}
```

### Conditional UI Based on Auth

```typescript
function Navigation() {
  const { isAuthenticated, user, signIn, signOut } = useAuth();

  return (
    <nav>
      {isAuthenticated ? (
        <>
          <span>{user?.name}</span>
          <button onClick={signOut}>Logout</button>
        </>
      ) : (
        <button onClick={signIn}>Login</button>
      )}
    </nav>
  );
}
```

## Testing

### Test Commands

```bash
cd packages/auth

pnpm test                    # Run all tests
pnpm test:watch              # Watch mode
pnpm test:coverage           # Coverage report
```

### Mocking useAuth in Tests

```typescript
import { vi } from "vitest";

vi.mock("@mbe/auth/react", () => ({
  useAuth: () => ({
    isLoading: false,
    isAuthenticated: true,
    user: { id: "123", email: "test@example.com", name: "Test User" },
    accessToken: "mock-token",
    signIn: vi.fn(),
    signOut: vi.fn(),
  }),
}));
```

### Testing Fastify Routes with Auth

```typescript
import { buildApp } from "../app.js";

describe("Protected Routes", () => {
  it("should return 401 without auth header", async () => {
    const app = await buildApp();
    const response = await app.inject({
      method: "GET",
      url: "/v1/users/me",
    });
    expect(response.statusCode).toBe(401);
  });
});
```

## Development Commands

```bash
cd packages/auth

pnpm dev          # Watch mode build
pnpm build        # Build package
pnpm lint         # ESLint check
pnpm typecheck    # TypeScript validation
pnpm test         # Run tests
```

## Auth Flow Summary

```
Frontend Login:
1. User clicks login → signIn() called
2. Redirect to Auth0 login page
3. Auth0 authenticates → redirects to callback URL
4. AuthProvider processes callback, stores tokens
5. useAuth() returns isAuthenticated: true

API Call:
1. useAccessToken() provides Bearer token
2. Request sent with Authorization header
3. Fastify authPlugin validates JWT via JWKS
4. request.user populated with decoded claims
5. Route handler accesses user info

Logout:
1. signOut() called → redirect to Auth0 logout
2. Tokens cleared from browser storage
3. Redirect back to app
```

## E2E Testing with Playwright

Playwright can test authenticated features using programmatic Auth0 login (Resource Owner Password Grant). This bypasses the browser login UI entirely — fast, reliable, CI-friendly.

### Required Environment Variables

```bash
E2E_AUTH0_DOMAIN=dev-ytbgmz5ls3wh4xdx.us.auth0.com
E2E_AUTH0_CLIENT_ID=<Auth0 client ID with Password grant enabled>
E2E_AUTH0_AUDIENCE=https://api.mattbutlerengineering.com
E2E_AUTH_EMAIL=<test user email>
E2E_AUTH_PASSWORD=<test user password>
```

### Auth0 Prerequisites

1. The Auth0 application must have the **Password** grant type enabled (Settings > Advanced > Grant Types)
2. The test user must use email/password (no MFA, no social login)
3. The default directory in Auth0 must be set to `Username-Password-Authentication`

### Using the authPage Fixture

```typescript
import { test, expect } from "./fixtures.js";

test("authenticated page works", async ({ authPage }) => {
  await authPage.goto("/reservations");
  await expect(authPage.getByTestId("dashboard-layout")).toBeVisible();
});
```

### How It Works

1. `injectAuth0Session()` fetches tokens via Auth0's `/oauth/token` endpoint (ROPC grant)
2. Tokens are injected into `sessionStorage` as an `oidc-client-ts` user entry
3. Page reloads — `react-oidc-context` AuthProvider reads the session and treats user as authenticated
4. No browser login flow, no Auth0 UI interaction, no consent screens

### Running E2E Tests

```bash
cd apps/hospitality
pnpm test:e2e  # Requires E2E_AUTH* env vars
```

## Quick Checklist

### Adding Auth to React App
1. [ ] Wrap app with `AuthProvider`
2. [ ] Configure OIDC settings from env vars
3. [ ] Use `useAuth()` for auth state
4. [ ] Handle loading state before rendering
5. [ ] Use `useAccessToken()` for API calls

### Adding Auth to Fastify Route
1. [ ] Register `authPlugin` or use inline JWT verification
2. [ ] Add `preHandler: requireAuth` for protected routes
3. [ ] Add `security: [{ bearerAuth: [] }]` to schema
4. [ ] Access user via `request.user`
5. [ ] Handle 401 responses for unauthenticated requests

Files in this skill

  • SKILL.md11.3 KB
  • source.json780 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.