> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hoodcloud.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Clerk Authentication Setup

> Configure Clerk as an external authentication provider for HoodCloud

## Overview

HoodCloud uses [Clerk](https://clerk.com) as the primary authentication provider. Clerk enables authentication via email, social login, wallet-based sign-in, and other identity methods.

When Clerk is enabled (default):

* Clerk issues JWTs that HoodCloud validates via JWKS
* User accounts are created and updated automatically via Clerk webhooks
* Users can optionally register a wallet (Ethereum or Solana) after authentication

## Prerequisites

* A Clerk account with an application created
* The auth-server deployed and accessible via HTTPS (required for webhook delivery)
* Database migration `020_external_auth_id` applied

## Configuration

### Auth Provider Modes

The `AUTH_PROVIDER` environment variable controls which JWT validators are active:

| Value   | Description                                  | Use Case                                     |
| ------- | -------------------------------------------- | -------------------------------------------- |
| `clerk` | Clerk-only (default)                         | All new and existing deployments             |
| `both`  | Multi-validator, tries Clerk first then SIWE | Migration period — supports both token types |
| `siwe`  | SIWE-only (legacy)                           | Legacy deployments not yet migrated to Clerk |

### Environment Variables

Set `AUTH_PROVIDER`, `AUTH_CLERK_SECRET_KEY`, `AUTH_CLERK_AUTHORIZED_PARTY` on both auth-server and api-server. Set `AUTH_CLERK_WEBHOOK_SIGNING_SECRET` on auth-server only.

> **Full reference with defaults and security classifications:** [Environment Variables](./environment-variables#clerk-primary-auth-provider)

### Example Configuration

```bash theme={null}
# Enable Clerk authentication
AUTH_PROVIDER=clerk

# Clerk credentials (store in Vault for production)
AUTH_CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxx
AUTH_CLERK_AUTHORIZED_PARTY=https://hoodcloud.io,https://app.hoodcloud.io
AUTH_CLERK_WEBHOOK_SIGNING_SECRET=whsec_xxxxxxxxxxxx
```

## Obtaining Clerk Credentials

### Secret Key

1. Log in to [Clerk Dashboard](https://dashboard.clerk.com)
2. Select your application
3. Go to **API Keys**
4. Copy the **Secret Key** (starts with `sk_live_` for production or `sk_test_` for development)
5. Store as `AUTH_CLERK_SECRET_KEY`

> **Note:** For production, store secrets in Vault at `secret/hoodcloud/app-credentials`. See [Vault Operations](./vault) for details.

### Authorized Party

The authorized party is the URL of your frontend application. Clerk includes this in the `azp` (authorized party) claim of issued JWTs. HoodCloud validates this claim to ensure tokens were issued for your application.

Set `AUTH_CLERK_AUTHORIZED_PARTY` to a comma-separated list of allowed frontend origins (e.g., `https://hoodcloud.io,https://app.hoodcloud.io`).

### Webhook Signing Secret

The signing secret is generated when you create a webhook endpoint in Clerk. See the next section for webhook setup.

## Webhook Configuration

Clerk webhooks notify HoodCloud when users are created, updated, or deleted. The auth-server processes these events at `POST /webhooks/clerk`.

### Setup in Clerk Dashboard

1. Go to **Webhooks** in the Clerk Dashboard
2. Click **Add Endpoint**
3. Set the endpoint URL to your auth-server's public URL:
   ```
   https://<auth-server-host>/webhooks/clerk
   ```
4. Subscribe to these events:
   * `user.created`
   * `user.updated`
   * `user.deleted`
5. Click **Create**
6. Copy the **Signing Secret** (starts with `whsec_`)
7. Store as `AUTH_CLERK_WEBHOOK_SIGNING_SECRET`

### Webhook Events

| Event          | Action                       | Details                                                                                                   |
| -------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| `user.created` | Creates a local user account | Sets `external_auth_id` to Clerk user ID, syncs primary email. Idempotent — skips if user already exists. |
| `user.updated` | Syncs email changes          | Updates local email if primary email changed in Clerk. No-op if user not found locally.                   |
| `user.deleted` | Audit logging                | Logs the deletion event. Does not delete the local user record.                                           |

### Webhook Security

Webhooks are verified using [Svix](https://www.svix.com/) HMAC-SHA256 signature verification. The auth-server rejects any request with an invalid or missing signature.

The webhook endpoint:

* Does not require JWT authentication
* Does not have rate limiting (Clerk controls delivery rate)
* Is mounted conditionally — only when `AUTH_PROVIDER` is `clerk` or `both`

## User Lifecycle

### Clerk-Authenticated Users

1. User signs up or logs in via Clerk (email, social, etc.)
2. Clerk sends `user.created` webhook → HoodCloud creates local user with `external_auth_id`
3. User's frontend receives a Clerk JWT
4. Frontend sends JWT to HoodCloud API → Clerk validator verifies via JWKS and resolves to local user ID
5. (Optional) User registers an Ethereum wallet via `POST /api/v1/wallet/register-public-key`

### Wallet Registration

Clerk-authenticated users can register an Ethereum or Solana wallet:

**Endpoint:** `POST /api/v1/wallet/register-public-key`

**Request (Ethereum):**

```json theme={null}
{
  "message": "Prove you own 0xABC...123: <user-uuid>",
  "signature": "0x..."
}
```

**Request (Solana):**

```json theme={null}
{
  "message": "Prove you own <base58-address>: <user-uuid>",
  "signature": "<base58-encoded-signature>"
}
```

* The message must contain the authenticated user's UUID
* **Ethereum:** The signature must be a valid EIP-191 `personal_sign` over the message
* **Solana:** The signature must be a valid Ed25519 signature over the message bytes
* The server auto-detects the wallet type from the address format and verifies accordingly

**Response (200):**

```json theme={null}
{
  "status": "ok",
  "address": "0xABC...123"
}
```

## Migration Path: SIWE to Clerk

### Phase 1: Enable Multi-Provider Mode

Deploy with both validators active:

```bash theme={null}
AUTH_PROVIDER=both
AUTH_CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxx
AUTH_CLERK_AUTHORIZED_PARTY=https://hoodcloud.io,https://app.hoodcloud.io
AUTH_CLERK_WEBHOOK_SIGNING_SECRET=whsec_xxxxxxxxxxxx
```

In this mode:

* Existing SIWE users continue authenticating with wallet signatures
* New Clerk users are created via webhooks
* The multi-validator tries Clerk first, then falls back to SIWE
* Both token types are accepted on all API endpoints

### Phase 2: Migrate Frontend

1. Integrate Clerk SDK into the frontend
2. Update sign-in flows to use Clerk
3. Existing SIWE sessions remain valid until they expire
4. New sessions use Clerk JWTs

### Phase 3: Switch to Clerk-Only

Once all users are migrated:

```bash theme={null}
AUTH_PROVIDER=clerk
```

This disables SIWE JWT validation. The SIWE auth endpoints (`/auth/nonce`, `/auth/verify`) remain on the auth-server but issued tokens will no longer be accepted by the API server.

## Database Changes

Migration `020_external_auth_id` makes the following schema changes:

| Change                                     | Purpose                                                    |
| ------------------------------------------ | ---------------------------------------------------------- |
| `wallet_address` nullable                  | Allows users without Ethereum wallets (email/social login) |
| `external_auth_id` column (VARCHAR 255)    | Stores Clerk user ID (e.g., `user_2abc123`)                |
| Unique partial index on `external_auth_id` | Efficient lookup, allows multiple NULL values              |

These changes are backward-compatible. Existing SIWE users retain their `wallet_address` and have `external_auth_id = NULL`.

## Troubleshooting

### Webhook signature verification fails

**Symptom:** Auth-server logs `Failed to verify webhook signature`

**Causes:**

* `AUTH_CLERK_WEBHOOK_SIGNING_SECRET` does not match the signing secret in Clerk Dashboard
* The webhook endpoint URL in Clerk does not match the auth-server's actual URL
* A proxy or load balancer is modifying request headers

**Fix:** Verify the signing secret matches. Re-create the webhook endpoint in Clerk if needed.

### User not found after Clerk login

**Symptom:** API returns authentication errors for Clerk-authenticated users. Validator logs `no local account for clerk user`.

**Causes:**

* The `user.created` webhook was not delivered or failed
* Webhook endpoint is not reachable from Clerk's servers
* `AUTH_CLERK_WEBHOOK_SIGNING_SECRET` is missing (webhook handler not initialized)

**Fix:**

1. Check Clerk Dashboard → Webhooks → check delivery status for the endpoint
2. Verify the auth-server is reachable at the configured webhook URL
3. Check auth-server logs for webhook processing errors
4. Manually trigger a re-delivery from Clerk Dashboard if needed

### Authorized party mismatch

**Symptom:** Validator logs `verify authorized party: got "X", want one of [...]`

**Cause:** `AUTH_CLERK_AUTHORIZED_PARTY` does not include the `azp` claim origin from the Clerk JWT.

**Fix:** Add the missing frontend origin to `AUTH_CLERK_AUTHORIZED_PARTY` (comma-separated list).

### Multi-provider mode — wrong validator used

**Symptom:** SIWE tokens fail validation, or Clerk tokens fail validation in `both` mode.

**Note:** In `both` mode, the multi-validator tries Clerk first, then SIWE. If a SIWE token is passed to the Clerk validator, it will fail (expected), and the multi-validator will fall back to SIWE. This is normal behavior — no action needed unless both validators fail.
