# Implementation Guide

Step-by-step guide to integrate `@appdirect/auth-bff` into your application.

## Prerequisites

1. **AppDirect marketplace** with an API client configured
2. **Node.js 18+**
3. **Next.js 13.4+** (App Router) or any **Node.js** serverless / API route host

## AppDirect marketplace setup

### 1. Create an API client

In AppDirect Marketplace Manager:

- Create an API client and note the **client ID** and **client secret**
- Enable **Authorization Code** grant
- Scopes: `ROLE_USER`, `openid`, `profile`, `email`, `company` (add `global_header` only if using Global Header / `/api/auth/jwt`)
- Enable Basic Info / userinfo if your marketplace uses it

### 2. Register redirect URI

Register the OAuth callback URL for your app:

| Environment | Redirect URI |
|-------------|--------------|
| Local | `http://localhost:3000/api/auth/callback` |
| Production | `https://your-domain.com/api/auth/callback` |

The redirect URI must **exactly match** what the SDK sends (`{appBaseUrl}{callbackPath}`).

### 3. Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `APPDIRECT_ISSUER_BASE_URL` | Yes | Marketplace URL, e.g. `https://marketplace.example.com` |
| `APPDIRECT_CLIENT_ID` | Yes | OAuth client ID |
| `APPDIRECT_CLIENT_SECRET` | Yes | OAuth client secret |
| `NEXT_PUBLIC_APP_URL` | Next.js | Public app URL for redirect URI |
| `APP_BASE_URL` / `BASE_URL` | Handlers | App origin for redirect URI |

**Never commit secrets.** Use `.env.local` (Next.js) or your host's environment variable settings.

---

## Next.js App Router integration

### Step 1: Install

```bash
npm install @appdirect/auth-bff next
```

Local SDK development:

```bash
npm install file:../appdirect-auth-bff
```

### Step 2: Create auth config

`lib/auth.ts`:

```typescript
import { createNextAuthHandlers } from '@appdirect/auth-bff/next';

export const authHandlers = createNextAuthHandlers({
  issuerBaseUrl: process.env.APPDIRECT_ISSUER_BASE_URL!,
  clientId: process.env.APPDIRECT_CLIENT_ID!,
  clientSecret: process.env.APPDIRECT_CLIENT_SECRET!,
  appBaseUrl: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
});
```

### Step 3: Add route handlers

Create these files (each is a thin wrapper):

| File | Export |
|------|--------|
| `app/api/auth/login/route.ts` | `export const GET = (req) => authHandlers.login(req)` |
| `app/api/auth/callback/route.ts` | `export const GET = (req) => authHandlers.callback(req)` |
| `app/api/auth/me/route.ts` | `export const GET = (req) => authHandlers.me(req)` |
| `app/api/auth/jwt/route.ts` | `export const GET = (req) => authHandlers.jwt(req)` |
| `app/api/auth/refresh/route.ts` | `export const GET/POST = (req) => authHandlers.refresh(req)` |
| `app/api/auth/logout/route.ts` | `export const GET/POST = (req) => authHandlers.logout(req)` |

See [examples/nextjs-app-router](../examples/nextjs-app-router) for a complete project.

### Step 4: Environment file

`.env.local`:

```env
APPDIRECT_ISSUER_BASE_URL=https://marketplace.example.com
APPDIRECT_CLIENT_ID=your-client-id
APPDIRECT_CLIENT_SECRET=your-client-secret
NEXT_PUBLIC_APP_URL=http://localhost:3000
```

### Step 5: Frontend login link

```html
<a href="/api/auth/login">Log in with AppDirect</a>
```

### Step 6: Check auth state

```typescript
const res = await fetch('/api/auth/me', { credentials: 'include' });
const { user, hasSession } = await res.json();

if (user) {
  console.log(user.id, user.email, user.name);
}
```

### Step 7 (optional): Middleware for protected routes

`middleware.ts`:

```typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const session = request.cookies.get('session');
  if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/api/auth/login', request.url));
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*'],
};
```

Middleware only checks cookie **presence**. Full validation happens in route handlers.

### Runtime

Route handlers use Node.js APIs by default. If you set `export const runtime = 'edge'`, verify all SDK dependencies work on Edge (v1 targets Node.js).

---

## Node API route handlers integration

### Step 1: Install

```bash
npm install @appdirect/auth-bff
```

### Step 2: Centralize config

`lib/auth.js`:

```javascript
import { createAuthRouteHandlers } from '@appdirect/auth-bff/handlers';

export const handlers = createAuthRouteHandlers({
  issuerBaseUrl: process.env.APPDIRECT_ISSUER_BASE_URL,
  clientId: process.env.APPDIRECT_CLIENT_ID,
  clientSecret: process.env.APPDIRECT_CLIENT_SECRET,
  appBaseUrl:
    process.env.APP_BASE_URL ?? process.env.BASE_URL ?? 'http://localhost:3000',
});
```

### Step 3: API routes

`api/auth/login.js`:

```javascript
import { handlers } from '../../lib/auth.js';

export default function handler(req, res) {
  return handlers.login(req, res);
}
```

Repeat for `callback`, `me`, `jwt`, `refresh`, `logout`.

See [examples/handlers](../examples/handlers).

### Step 4: Custom protected API routes

For marketplace or billing proxies outside the built-in auth handlers, use the same cookie helpers the SDK handlers use internally. See [Custom API routes](USAGE.md#custom-api-routes) in the Usage Reference.

`lib/session.ts` (from [appdirect-auth-example](../../appdirect-auth-example)):

```typescript
import { readAuthCookies } from '@appdirect/auth-bff/handlers';
import type { CookieMutation } from '@appdirect/auth-bff';
import { NextResponse } from 'next/server';
import { auth } from './auth';

export async function resolveSession(req: Request) {
  const { session, refreshToken } = readAuthCookies(req.headers.get('cookie'));
  if (!session && !refreshToken) return null;

  const result = await auth.getFeJwt({ sessionToken: session, refreshToken });
  return { token: result.token, cookiesToSet: result.cookiesToSet };
}
```

For Node `createAuthRouteHandlers`, use `applyCookieMutations` + `createHandlerCookieWriter(res)` instead of `NextResponse.cookies.set`. Use `result.token` as `Authorization: Bearer` for upstream AppDirect API calls. Do not hand-roll cookie parsing or `Set-Cookie` formatting — the SDK helpers match handler behavior (including `appendHeader` for multiple cookies).

### Step 5: Run locally

Use your host's local dev tooling (e.g. `vercel dev` for file-based `api/` routes on Vercel).

---

## AppDirect Global Header integration

The Global Header widget needs a FEJWT from your BFF. Add `global_header` to your SDK config — it is not included in default scopes:

```typescript
createAppDirectAuth({
  // ...
  scopes: ['company', 'openid', 'profile', 'email', 'global_header'],
});
```

```javascript
window.ad_global_header_config = {
  header: { renderTo: '#global-header-container' },
  userId: '<user-id>',
  remoteMarketUrl: 'https://your-marketplace.example.com',
  useFeJwt: true,
  feJwtURL: '/api/auth/jwt',
  fetchJWTTokenFunction: async function () {
    return fetch('/api/auth/jwt', {
      method: 'GET',
      credentials: 'include',
    });
  },
};
```

The `/api/auth/jwt` endpoint:
- Returns `{ token: "<FEJWT>" }` when session is valid
- Auto-refreshes using `refresh_token` cookie when FEJWT is expired
- Returns `401` with `{ error: "session_expired", refresh_url: "/api/auth/refresh" }` when lazy renewal fails (including `/auth/refresh` HTTP errors)

Explicit `GET/POST /api/auth/refresh` still returns `502` with `{ error: "refresh_failed" }` when the exchange fails.

---

## Troubleshooting

### Redirect URI mismatch

**Symptom:** `token_exchange` error after login.

**Fix:** Ensure `appBaseUrl` + `callbackPath` exactly matches the URI registered in AppDirect. Check `NEXT_PUBLIC_APP_URL` / `BASE_URL` in production.

### Callback redirects to `localhost` on Vercel

**Symptom:** After OAuth, `Location` is `https://localhost:3000/` even though the app runs on a Vercel URL.

**Fix:** Set `NEXT_PUBLIC_APP_URL` to your public deployment URL (e.g. `https://your-app.vercel.app`). Both adapters redirect to `appBaseUrl` after login, logout, and OAuth errors — not `req.url` (which can report an internal host on serverless).

### `server_config` on login

**Fix:** Verify `APPDIRECT_ISSUER_BASE_URL` and `APPDIRECT_CLIENT_ID` are set.

### `invalid_state` on callback

**Fix:** Ensure cookies are enabled. The `oauth_state` cookie must survive the redirect to AppDirect and back. Check SameSite settings if using cross-site flows.

### `/api/auth/me` returns `user: null` but `hasSession: true`

The FEJWT may not contain decodable user claims. The frontend can fall back to `/api/auth/jwt` and decode the JWT client-side for `sub` and display name.

### FEJWT / Global Header fails

**Fix:** Confirm scopes include `global_header` and the API client has FEJWT permissions. Check `/api/auth/jwt` response in browser devtools.

### Multiple `Set-Cookie` headers overwritten

**Symptom:** After login or refresh, only one cookie is set (e.g. `session` but not `refresh_token`).

**Fix:** Node's `res.setHeader('Set-Cookie', ...)` replaces prior values. Use `res.appendHeader('Set-Cookie', ...)` (Node 18+) for each cookie. The SDK's `createAuthRouteHandlers` and `applyCookieMutations` do this automatically when `appendHeader` is available on the response object.

---

## Reference app: appdirect-auth-example

The sibling [appdirect-auth-example](../../appdirect-auth-example) project is the living reference integration:

- **Next.js 15 App Router** (TypeScript) with client components for the dashboard UI
- Auth via `createNextAuthHandlers()` from `@appdirect/auth-bff/next` in `lib/auth.ts`
- Custom routes (`/api/users`, `/api/subscriptions`) use `lib/session.ts` with SDK cookie helpers
- Users API and Billing API (subscription change) demos with lazy-loaded dashboard tabs

To run locally:

```bash
cd ../appdirect-auth-bff && npm run build
cd ../appdirect-auth-example && npm install && npm run dev
```

Install the SDK as a local file dependency:

```bash
npm install file:../appdirect-auth-bff
```
