Backend: JWT middleware validates Clerk tokens on every request, extracts org ID from claims, enforces org-scoped queries via Supabase RLS. Frontend: ClerkProvider wraps the app, auth gate blocks unauthenticated access, UserButton in header, token injected into every API call. Supabase production wired to trust Clerk JWTs via Third-Party Auth integration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
187 lines
5.7 KiB
Markdown
187 lines
5.7 KiB
Markdown
# Phase 3 Setup Checklist
|
|
## Clerk + Supabase + Railway Staging
|
|
|
|
Run these steps in order. Code is already written — you just need the keys.
|
|
|
|
---
|
|
|
|
## Step 1: Clerk Account Setup
|
|
|
|
1. Go to clerk.com and create an account
|
|
2. Create a new application — name it **Signal**
|
|
3. Select **React** as the framework
|
|
4. From **Dashboard > API Keys**, copy:
|
|
- **Publishable Key** (starts with `pk_test_...`) — goes in the frontend `.env`
|
|
- **Secret Key** (starts with `sk_test_...`) — NOT needed for our approach (we use JWKS)
|
|
5. From **Dashboard > API Keys > Show API URLs**, copy the **Frontend API URL**
|
|
- Looks like: `https://clean-mayfly-62.clerk.accounts.dev`
|
|
- Your JWKS URL is: `<Frontend API URL>/.well-known/jwks.json`
|
|
- Example: `https://clean-mayfly-62.clerk.accounts.dev/.well-known/jwks.json`
|
|
- This goes in Railway as `CLERK_JWKS_URL`
|
|
|
|
6. From **Dashboard > Configure > Organizations**, enable Organizations
|
|
- Create one organization named **Gaboro DME** with slug `gaboro-pilot`
|
|
- Copy the Organization ID (starts with `org_...`) — you'll need it for the SQL step below
|
|
- Add yourself as a member of that organization
|
|
|
|
7. From **Dashboard > Configure > Domains**, add:
|
|
- `http://localhost:5173` (local dev)
|
|
- Your Vercel production URL when it's live
|
|
|
|
---
|
|
|
|
## Step 2: Supabase Staging Project
|
|
|
|
1. Go to supabase.com, create a new project named **signal-staging**
|
|
2. Wait for it to provision (2-3 minutes)
|
|
3. Go to **Settings > API**, copy:
|
|
- **Project URL** (looks like `https://xxxxxxxxxxxx.supabase.co`)
|
|
- **Service Role Key** — must be the long `eyJ...` JWT format (NOT the `sb_` format)
|
|
4. Go to **SQL Editor**, run the migration SQL below
|
|
|
|
---
|
|
|
|
## Step 3: SQL Migration
|
|
### Run on BOTH Supabase projects (production + staging)
|
|
|
|
```sql
|
|
-- Add Clerk org ID column to organizations table
|
|
ALTER TABLE organizations
|
|
ADD COLUMN IF NOT EXISTS clerk_org_id TEXT UNIQUE;
|
|
|
|
-- Update the pilot org with the real Clerk org ID
|
|
-- Replace org_xxxxxxxxxxxxxxxx with the actual org ID from Clerk Dashboard
|
|
UPDATE organizations
|
|
SET clerk_org_id = 'org_xxxxxxxxxxxxxxxx'
|
|
WHERE slug = 'gaboro-pilot';
|
|
|
|
-- Helper function for RLS policies (reads Clerk org ID from JWT)
|
|
CREATE OR REPLACE FUNCTION requesting_owner_id()
|
|
RETURNS TEXT
|
|
LANGUAGE SQL
|
|
STABLE
|
|
AS $$
|
|
SELECT COALESCE(
|
|
(auth.jwt() -> 'o') ->> 'id',
|
|
auth.jwt() ->> 'sub'
|
|
);
|
|
$$;
|
|
|
|
-- RLS on organizations
|
|
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "org_members_see_own_org"
|
|
ON organizations FOR SELECT
|
|
USING (clerk_org_id = requesting_owner_id());
|
|
|
|
-- RLS on upload_batches
|
|
ALTER TABLE upload_batches ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "org_members_see_own_batches"
|
|
ON upload_batches FOR SELECT
|
|
USING (org_id IN (
|
|
SELECT id FROM organizations WHERE clerk_org_id = requesting_owner_id()
|
|
));
|
|
CREATE POLICY "org_members_insert_own_batches"
|
|
ON upload_batches FOR INSERT
|
|
WITH CHECK (org_id IN (
|
|
SELECT id FROM organizations WHERE clerk_org_id = requesting_owner_id()
|
|
));
|
|
|
|
-- RLS on normalized_records
|
|
ALTER TABLE normalized_records ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "org_members_see_own_records"
|
|
ON normalized_records FOR SELECT
|
|
USING (EXISTS (
|
|
SELECT 1 FROM upload_batches ub
|
|
JOIN organizations o ON o.id = ub.org_id
|
|
WHERE ub.id = normalized_records.batch_id
|
|
AND o.clerk_org_id = requesting_owner_id()
|
|
));
|
|
|
|
-- RLS on report_runs
|
|
ALTER TABLE report_runs ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "org_members_see_own_runs"
|
|
ON report_runs FOR SELECT
|
|
USING (org_id IN (
|
|
SELECT id FROM organizations WHERE clerk_org_id = requesting_owner_id()
|
|
));
|
|
|
|
-- RLS on export_files
|
|
ALTER TABLE export_files ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "org_members_see_own_exports"
|
|
ON export_files FOR SELECT
|
|
USING (EXISTS (
|
|
SELECT 1 FROM report_runs rr
|
|
JOIN organizations o ON o.id = rr.org_id
|
|
WHERE rr.id = export_files.report_run_id
|
|
AND o.clerk_org_id = requesting_owner_id()
|
|
));
|
|
|
|
-- NOTE: source_files and mapping_decisions are write-once audit records.
|
|
-- The backend uses the service role key for writes (bypasses RLS by design).
|
|
-- Add SELECT policies here when frontend reads are needed in a later phase.
|
|
```
|
|
|
|
---
|
|
|
|
## Step 4: Connect Supabase to Clerk (Native Integration)
|
|
|
|
### In Clerk Dashboard:
|
|
1. Go to **Configure > Integrations** (or search "Supabase")
|
|
2. Click **Activate Supabase integration**
|
|
3. Copy your **Clerk domain** shown there
|
|
|
|
### In Supabase Dashboard (repeat for BOTH projects):
|
|
1. Go to **Authentication > Sign In / Sign Up > Third Party Auth**
|
|
2. Click **Add provider** > select **Clerk**
|
|
3. Paste the Clerk domain
|
|
4. Save
|
|
|
|
This makes Supabase trust Clerk JWTs — no custom templates needed.
|
|
|
|
---
|
|
|
|
## Step 5: Set Railway Environment Variables
|
|
|
|
### Production environment:
|
|
In Railway > signal-api > production > Variables, add:
|
|
```
|
|
CLERK_JWKS_URL = https://eternal-goblin-1.clerk.accounts.dev/.well-known/jwks.json
|
|
```
|
|
|
|
### Staging environment:
|
|
In Railway > signal-api > staging > Variables, add:
|
|
```
|
|
CLERK_JWKS_URL = https://eternal-goblin-1.clerk.accounts.dev/.well-known/jwks.json
|
|
SUPABASE_URL = https://<staging-project>.supabase.co
|
|
SUPABASE_ANON_KEY = eyJ... (anon key from staging project)
|
|
SUPABASE_SERVICE_KEY = eyJ... (service role key from staging project)
|
|
```
|
|
Staging already has SIGNAL_API_KEY and ALLOWED_ORIGINS set.
|
|
|
|
---
|
|
|
|
## Step 6: Set Frontend .env
|
|
|
|
Edit `signal-ui/.env`:
|
|
```
|
|
VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxx
|
|
```
|
|
|
|
---
|
|
|
|
## Step 7: Deploy Staging Backend
|
|
|
|
Once all Railway staging vars are set, trigger a deploy:
|
|
```bash
|
|
cd /Users/sttil-solutions/projects/signal
|
|
railway up --detach --environment staging -m "Phase 3: Clerk auth + staging env"
|
|
```
|
|
|
|
---
|
|
|
|
## Staging API Key (save this)
|
|
|
|
The staging environment uses a separate API key for direct testing:
|
|
```
|
|
r0C6vm1RLeg84lB9jeZ5sSRieDZWsLL0dAMDfEcXubE
|
|
```
|