# PERS Authentication Guide

This guide covers authentication and authorization patterns in the PERS API.

## Architecture Overview

PERS uses a tiered security architecture designed for flexibility and security:

1. **Tenant Context (`X-Project-Key`)**: Identifies the project/tenant. Required for almost all requests (Public & Authenticated).
2. **Authentication (JWT)**: Identifies the principal (User, Business, or Admin).
  - **Users**: Authenticated via JWTs obtained through Token Exchange.
  - **Businesses**: Authenticated via JWTs.
  - **Admins**: Authenticated via Tenant-scoped JWTs.
3. **Token Binding (DPoP)**: Binds access tokens to a client-held private key (Optional but Highly Recommended).


> **Note**: We have deprecated legacy `x-api-key` usage for business logic authentication in favor of strict JWT-based flows. `X-Project-Key` is solely for tenant context resolution.


## 1. Project Identification (Public Access)

All API requests must identify the target tenant/project context using the Project Key.

- **Header**: `X-Project-Key`
- **Purpose**: Resolves the tenant configuration, feature flags, and ensures data isolation.
- **Scope**: By itself, grants access to **Public** endpoints (e.g., browsing catalogs, basic availability checks).
- **Format**: 64-character hex string. For testnet campaigns, prefix with `pk_test_`.


```http
GET /campaigns
X-Project-Key: a1b2c3d4e5f6789...
```

## 2. Authentication (JWT)

Authenticated operations require a valid JSON Web Token (JWT) in the `Authorization` header.

- **Header**: `Authorization: Bearer <token>`
- **Format**: Standard JWT (JSON Web Token).


### Unified Token Endpoint

Obtain PERS-native JWTs by exchanging external credentials at the unified auth endpoint.

**Endpoint**: `POST /auth/token`

#### Request Body (`SessionAuthRequestDTO`)

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `authToken` | string | Yes | The proof from the external provider (e.g., Firebase ID Token, DFNS Auth Token, etc.). |
| `authType` | string | No | The type of principal. Defaults to `'user'`. Values: `'user'`, `'tenant'` (admin), `'business'`. |
| `context` | object | No | Context selection for multi-membership users. |
| `context.businessId` | string | No | Explicit business ID when user has multiple business memberships. |
| `context.tenantId` | string | No | Explicit tenant ID for multi-tenant admin auth. |


#### Example: User Login (Token Exchange)

```http
POST /auth/token
Host: api.pers.ninja
X-Project-Key: a1b2c3d4e5f6789...
Content-Type: application/json

{
  "authToken": "external_provider_jwt_token",
  "authType": "user"
}
```

**Response**:

```json
{
  "accessToken": "pers_access_jwt...",
  "refreshToken": "pers_refresh_jwt...",
  "user": { ... }
}
```

#### Example: Business Login (With Context Selection)

When authenticating as a business member, include the `context` object:

```http
POST /auth/token
Host: api.pers.ninja
X-Project-Key: pk_test_abc123...
Content-Type: application/json

{
  "authToken": "passkey_jwt_token",
  "authType": "business",
  "context": {
    "businessId": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

**Response**:

```json
{
  "accessToken": "pers_business_jwt...",
  "refreshToken": "pers_refresh_jwt...",
  "user": { ... },
  "business": { "id": "...", "displayName": "Acme Corp", ... }
}
```

> **Note**: If user has multiple business memberships and `context.businessId` is not provided, the API returns a `MULTIPLE_CONTEXT_SELECTION_REQUIRED` error with available options.


## 3. DPoP (Demonstrating Proof-of-Possession)

**Status**: Optional but **Highly Recommended** for enhanced security.

DPoP binds an access token to a private key held by the client, preventing token replay attacks if the token is leaked. The PERS API supports DPoP validation on authentication and protected resource requests.

### How Verification Works

1. **Client** generates a temporary key pair (e.g., ES256).
2. **Client** creates a DPoP proof (a JWT signed with the private key) containing the HTTP method and URI.
3. **Client** sends the `dpop` header with the proof.
4. **Server** binds the issued access token to the public key in the DPoP proof.
5. **Subsequent Requests**: Client must send a new DPoP proof with each request using the bonded token.


### Implementation Guide

#### Step 1: Generate DPoP Proof

Use a library or generate a standard DPoP JWT header:

- **Typ**: `dpop+jwt`
- **Alg**: `ES256` (recommended)
- **Jwk**: Public key


**Claims**:

- `htm`: HTTP Method (e.g., `POST`)
- `htu`: HTTP URI (e.g., `https://api.pers.ninja/v2/auth/token`)
- `iat`: Issued At (timestamp)
- `jti`: Unique ID


#### Step 2: Include Header

```http
POST /auth/token
X-Project-Key: ...
dpop: <dpop-proof-jwt>
...
```

The server will embed the DPoP key thumbprint into the returned `accessToken`.

## Authentication Methods Summary

| Auth Type | Headers Required | Use Case |
|  --- | --- | --- |
| **Public** | `X-Project-Key` | Viewing public campaigns, checking user existence, public catalogs. |
| **User** | `X-Project-Key``Authorization: Bearer <user_jwt>` | User-specific operations, claim rewards, view simplified profile. |
| **Business** | `X-Project-Key``Authorization: Bearer <biz_jwt>` | Managing business assets, campaigns (if delegated). |
| **Admin** | `Authorization: Bearer <admin_jwt>`*(Tenant context often embedded)* | Full tenant administration, configuration. |


### Note on Legacy API Keys

Older integrations may have used `x-api-key` for authentication. This pattern is deprecated. Please migrate to the **JWT** pattern using the `/auth/token` exchange flow for all secure operations.

## Role-Based Access Control (RBAC)

Some endpoints require a minimum membership role within a business context. The role hierarchy (highest to lowest):

| Role | Description |
|  --- | --- |
| **OWNER** | Full control - can delete entity, transfer ownership |
| **ADMIN** | Administrative access - manage members & settings |
| **EDITOR** | Create/modify content, cannot manage members |
| **VIEWER** | Read-only access |


### How Role Checks Work

1. **Role is baked into JWT**: When authenticating as a business member, your role is included in the JWT token at login time.
2. **Guard validates role**: The `GlobalAuthGuard` checks `minBusinessRole` requirements without additional service calls.
3. **TENANT admins bypass**: Tenant administrators have full access regardless of `minBusinessRole` settings.


### API Documentation Pattern

Endpoints with role requirements document them in the description:

```
Requires BUSINESS auth (min role: ADMIN) or TENANT admin.
```

### Example: Membership Endpoints

| Endpoint | Min Role | Description |
|  --- | --- | --- |
| `GET /businesses/:id/members` | VIEWER | List members (any member can view) |
| `POST /businesses/:id/members` | ADMIN | Add member (admin only) |
| `PUT /businesses/:id/members/:userId` | ADMIN | Update role (admin only) |
| `DELETE /businesses/:id/members/:userId` | ADMIN | Remove member (admin only) |


## Mixed Authentication Endpoints

Many PERS endpoints support multiple authentication methods, providing flexibility for different integration scenarios.

### Example: File Upload Endpoint

The `/files/entity-storage-url` endpoint accepts multiple authentication types:

#### Business Integration

```javascript
// JWT authentication for business file uploads
const response = await fetch('https://api.pers.ninja/v2/files/entity-storage-url', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <business_jwt_token>',
    'X-Project-Key': 'a1b2c3d4e5f6789...',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    entityType: 'CAMPAIGN',
    operation: 'upload'
  })
});
```

#### User Integration

Authenticate first, then use the access token.

```javascript
// 1. Exchange Token
const authResponse = await fetch('/auth/token', { ... }); 
// 2. Use Access Token
const response = await fetch('/files/entity-storage-url', {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-Project-Key': '...',
  },
  ...
});
```

## Client Libraries & SDKs

### **@explorins/pers-shared**

The official TypeScript library providing type-safe contracts, DTOs, and error handling for PERS API integration.

#### Installation

```bash
npm install @explorins/pers-shared
```

#### Usage with New Auth Patterns

The library includes DTOs for the unified authentication flow:

```typescript
import { 
  SessionAuthRequestDTO, 
  SessionAuthResponseDTO,
  AccountOwnerType 
} from '@explorins/pers-shared';

// 1. Prepare Authentication Request
const loginRequest: SessionAuthRequestDTO = {
  authToken: "firebase_id_token_or_other_proof",
  authType: AccountOwnerType.USER // or BUSINESS / TENANT
};

// 2. Call Unified Endpoint
// (Implementation depends on your HTTP client)
const response = await httpClient.post<SessionAuthResponseDTO>(
  '/auth/token', 
  loginRequest,
  {
    headers: { 'X-Project-Key': 'a1b2c3d4e5f6789...' }
  }
);

// 3. Use Tokens
const { accessToken, refreshToken, user } = response.data;
```

## Counterfactual Wallets & User Signing

PERS implements a counterfactual wallet system that provides users with blockchain addresses before their first transaction.

**[Counterfactual Wallets & Signing Accounts Guide →](/6.counterfactual-wallets-guide)**

## Error Handling

PERS API uses a centralized error handling system via the ErrorFactory pattern.

### Error Response Categories

| Category | HTTP Status | Description | Retryable |
|  --- | --- | --- | --- |
| `SECURITY` | 401 | Authentication missing or expired | No |
| `SECURITY` | 403 | Permissions insufficient | No |
| `VALIDATION` | 400 | Invalid payload | No |
| `RATE_LIMIT` | 429 | Too many requests | Yes |


### Example: Authentication Required (401)

```json
{
  "status": 401,
  "code": "AUTHENTICATION_REQUIRED",
  "category": "SECURITY",
  "message": "Please provide valid authentication credentials",
  "correlationId": "req-123..."
}
```

For a complete list of error codes, refer to the [Error Handling Guide](/7.error-handling).

## Security Best Practices

### **JWT Token Security**

- **DPoP**: Use DPoP to bind tokens to your client instance.
- **Storage**: Store JWT tokens securely.
- **HTTPS**: Always use HTTPS.


### **Project Key Security**

- **Context Only**: Remember `X-Project-Key` is for context, not user identity.
- **Rotation**: Rotate keys if they are exposed in unauthorized contexts.


## Getting Started

1. **Obtain Project Key**: Use your tenant's `X-Project-Key`.
2. **Authenticate**: Use `POST /auth/token` to exchange your provider's token for a PERS JWT.
3. **Secure**: Add `dpop` headers for maximum security.
4. **Call API**: Use the JWT in the `Authorization` header for protected endpoints.