npm package: @startsimpli/auth
Description: Shared authentication package for StartSimpli Next.js apps
Latest version: 0.4.6

# @startsimpli/auth

Shared authentication package for StartSimpli Next.js applications. Provides JWT-based authentication with Django backend integration.

## Features

- JWT access token authentication
- Automatic token refresh with refresh tokens (httpOnly cookies)
- React Context and hooks for client-side auth
- Server-side session validation for SSR and API routes
- Next.js middleware helpers
- Auth guards for API routes
- Permission/role checks (owner > admin > member > viewer)
- TypeScript support with full type safety

## Installation

This package is part of the StartSimpli monorepo. It uses NPM workspaces and TypeScript path aliases for direct source imports during development.

```bash
# In your Next.js app's package.json
{
  "dependencies": {
    "@startsimpli/auth": "*"
  }
}
```

## Django Backend Integration

This package is designed to work with the StartSimpli Django API (`start-simpli-api`).

**Required Django endpoints:**
- `POST /api/v1/auth/token/` - Login (returns JWT access token + sets refresh token cookie)
- `POST /api/v1/auth/token/refresh/` - Refresh access token using cookie
- `POST /api/v1/auth/logout/` - Logout
- `GET /api/v1/auth/me/` - Get current user data

**Token flow:**
1. Login returns `{ access: "jwt-token", user: {...} }`
2. Refresh token stored as httpOnly cookie (managed by Django)
3. Access token stored in memory (not localStorage)
4. Automatic refresh before expiration (default: 4 minutes)

## Usage

### Client-Side Authentication

#### 1. Setup Auth Provider

Wrap your app with `AuthProvider` in your root layout:

```tsx
// app/layout.tsx
'use client';

import { AuthProvider } from '@startsimpli/auth/client';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AuthProvider
          config={{
            apiBaseUrl: process.env.NEXT_PUBLIC_API_URL!,
            onSessionExpired: () => {
              window.location.href = '/login';
            },
          }}
        >
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}
```

#### 2. Use Authentication Hook

```tsx
// app/dashboard/page.tsx
'use client';

import { useAuth } from '@startsimpli/auth/client';

export default function DashboardPage() {
  const { user, isLoading, isAuthenticated, logout } = useAuth();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return <div>Please login</div>;
  }

  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}
```

#### 3. Login Form

```tsx
// app/login/page.tsx
'use client';

import { useState } from 'react';
import { useAuth } from '@startsimpli/auth/client';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const { login } = useAuth();
  const router = useRouter();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      await login(email, password);
      router.push('/dashboard');
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit">Login</button>
    </form>
  );
}
```

#### 4. Permission Checks

```tsx
'use client';

import { usePermissions } from '@startsimpli/auth/client';

export default function SettingsPage() {
  const { isAdmin, canEdit, currentRole } = usePermissions();

  return (
    <div>
      <h1>Settings</h1>
      <p>Your role: {currentRole}</p>

      {isAdmin() && (
        <button>Admin Settings</button>
      )}

      {canEdit() ? (
        <button>Edit</button>
      ) : (
        <p>View only</p>
      )}
    </div>
  );
}
```

### Server-Side Authentication

#### 1. Protect Server Components

```tsx
// app/dashboard/page.tsx
import { getServerSession } from '@startsimpli/auth/server';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const session = await getServerSession(process.env.API_BASE_URL!);

  if (!session) {
    redirect('/login');
  }

  return (
    <div>
      <h1>Welcome, {session.user.firstName}!</h1>
    </div>
  );
}
```

#### 2. Next.js Middleware

Protect routes with authentication middleware:

```ts
// middleware.ts
import { createAuthMiddleware } from '@startsimpli/auth/server';

export const middleware = createAuthMiddleware({
  apiBaseUrl: process.env.API_BASE_URL!,
  publicPaths: ['/login', '/register', '/forgot-password'],
  loginPath: '/login',
});

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};
```

#### 3. API Route Protection

Protect API routes with auth guards:

```ts
// app/api/users/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@startsimpli/auth/server';

export const GET = withAuth(async (request: NextRequest, token: string) => {
  const response = await fetch(`${process.env.API_BASE_URL}/api/v1/users/`, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  const data = await response.json();
  return NextResponse.json(data);
});
```

#### 4. Role-Based API Protection

```ts
// app/api/admin/users/route.ts
import { NextRequest } from 'next/server';
import { withRole } from '@startsimpli/auth/server';

async function getUserRole() {
  // Fetch user's current role from your API
  return 'admin';
}

export const GET = withRole(
  'admin',
  getUserRole,
  async (request: NextRequest, token: string) => {
    // Only accessible to admins
    return NextResponse.json({ message: 'Admin data' });
  }
);
```

### Making Authenticated API Calls

```tsx
'use client';

import { useAuth } from '@startsimpli/auth/client';

export default function DataFetcher() {
  const { getAccessToken } = useAuth();

  const fetchData = async () => {
    const token = await getAccessToken();

    if (!token) {
      console.error('No access token');
      return;
    }

    const response = await fetch('/api/data', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    return response.json();
  };

  return <button onClick={fetchData}>Fetch Data</button>;
}
```

## API Reference

### Client Exports

#### `AuthProvider`

React context provider for authentication.

```tsx
interface AuthProviderProps {
  children: ReactNode;
  config: AuthConfig;
  initialSession?: Session | null;
}
```

#### `useAuth()`

Hook to access authentication state and methods.

```tsx
interface UseAuthReturn {
  user: AuthUser | null;
  session: Session | null;
  isLoading: boolean;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
  getAccessToken: () => Promise<string | null>;
}
```

#### `usePermissions()`

Hook for permission/role checks.

```tsx
interface UsePermissionsReturn {
  hasRole: (requiredRole: CompanyRole, companyId?: string) => boolean;
  isOwner: (companyId?: string) => boolean;
  isAdmin: (companyId?: string) => boolean;
  canEdit: (companyId?: string) => boolean;
  canView: (companyId?: string) => boolean;
  currentRole: CompanyRole | null;
  currentCompanyId: string | null;
}
```

#### `AuthClient`

Low-level authentication client (used internally by hooks).

```tsx
class AuthClient {
  constructor(config: AuthConfig);
  login(email: string, password: string): Promise<Session>;
  logout(): Promise<void>;
  refreshToken(): Promise<string>;
  getCurrentUser(): Promise<AuthUser>;
  getSession(): Session | null;
  getAccessToken(): Promise<string | null>;
}
```

### Server Exports

#### `getServerSession()`

Get authenticated session from server-side cookies.

```tsx
async function getServerSession(apiBaseUrl: string): Promise<Session | null>;
```

#### `validateSession()`

Validate session and return user or null.

```tsx
async function validateSession(apiBaseUrl: string): Promise<AuthUser | null>;
```

#### `requireAuth()`

Require authenticated session (throws if not authenticated).

```tsx
async function requireAuth(apiBaseUrl: string): Promise<Session>;
```

#### `createAuthMiddleware()`

Create Next.js middleware for route protection.

```tsx
function createAuthMiddleware(config: AuthMiddlewareConfig): Middleware;
```

#### `withAuth()`

HOF to wrap API routes with auth guard.

```tsx
function withAuth<T>(
  handler: (request: NextRequest, token: string) => Promise<NextResponse<T>>
): (request: NextRequest) => Promise<NextResponse<T>>;
```

#### `withRole()`

HOF to wrap API routes with role-based guard.

```tsx
function withRole<T>(
  requiredRole: CompanyRole,
  getUserRole: () => Promise<CompanyRole | null>,
  handler: (request: NextRequest, token: string) => Promise<NextResponse<T>>
): (request: NextRequest) => Promise<NextResponse<T>>;
```

### Types

```tsx
type CompanyRole = 'owner' | 'admin' | 'member' | 'viewer';

interface AuthUser {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
  isEmailVerified: boolean;
  createdAt: string;
  updatedAt: string;
  companies?: Array<{
    id: string;
    name: string;
    role: CompanyRole;
  }>;
  currentCompanyId?: string;
}

interface Session {
  user: AuthUser;
  accessToken: string;
  expiresAt: number;
}

interface AuthConfig {
  apiBaseUrl: string;
  tokenRefreshInterval?: number;
  onSessionExpired?: () => void;
  onUnauthorized?: () => void;
}
```

## Role Hierarchy

Roles follow a hierarchy where higher roles inherit permissions from lower roles:

```
owner (4) > admin (3) > member (2) > viewer (1)
```

Use `hasRolePermission(userRole, requiredRole)` to check if a user has sufficient permissions.

## Testing

```bash
npm test              # Run tests
npm run test:watch    # Watch mode
npm run test:coverage # Coverage report
```

## Development

```bash
npm run type-check    # TypeScript validation
```

## License

Private package for StartSimpli monorepo.