Skip to main content

Overview

HoodCloud uses Clerk 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:
ValueDescriptionUse Case
clerkClerk-only (default)All new and existing deployments
bothMulti-validator, tries Clerk first then SIWEMigration period — supports both token types
siweSIWE-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

Example Configuration

# 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
  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 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

EventActionDetails
user.createdCreates a local user accountSets external_auth_id to Clerk user ID, syncs primary email. Idempotent — skips if user already exists.
user.updatedSyncs email changesUpdates local email if primary email changed in Clerk. No-op if user not found locally.
user.deletedAudit loggingLogs the deletion event. Does not delete the local user record.

Webhook Security

Webhooks are verified using Svix 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):
{
  "message": "Prove you own 0xABC...123: <user-uuid>",
  "signature": "0x..."
}
Request (Solana):
{
  "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):
{
  "status": "ok",
  "address": "0xABC...123"
}

Migration Path: SIWE to Clerk

Phase 1: Enable Multi-Provider Mode

Deploy with both validators active:
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:
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:
ChangePurpose
wallet_address nullableAllows 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_idEfficient 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.