|
| 1 | +# RoboLearn Auth Server |
| 2 | + |
| 3 | +OAuth 2.1 / OIDC authentication server using Better Auth. |
| 4 | + |
| 5 | +## Setup |
| 6 | + |
| 7 | +```bash |
| 8 | +cd auth-server |
| 9 | +npm install |
| 10 | +cp .env.example .env.local |
| 11 | +# Edit .env.local with your values |
| 12 | +npm run db:push |
| 13 | +# Seed the trusted public client (see below) |
| 14 | +npm run dev # http://localhost:3001 |
| 15 | +``` |
| 16 | + |
| 17 | +### Seed Trusted Public Client |
| 18 | + |
| 19 | +After running `db:push`, you **must** seed the trusted public client (`robolearn-public-client`) in your database. This client is pre-configured in the code but needs to exist in the database for OAuth flows to work. |
| 20 | + |
| 21 | +**Option 1: Run SQL directly (recommended)** |
| 22 | + |
| 23 | +Execute this SQL in your database (via psql, pgAdmin, or your database tool): |
| 24 | + |
| 25 | +```sql |
| 26 | +INSERT INTO oauth_application ( |
| 27 | + id, |
| 28 | + client_id, |
| 29 | + client_secret, |
| 30 | + name, |
| 31 | + redirect_urls, |
| 32 | + type, |
| 33 | + disabled, |
| 34 | + metadata, |
| 35 | + created_at, |
| 36 | + updated_at |
| 37 | +) VALUES ( |
| 38 | + 'robolearn-public-client-id', |
| 39 | + 'robolearn-public-client', |
| 40 | + NULL, -- No secret for public client (PKCE only) |
| 41 | + 'RoboLearn Public Client', |
| 42 | + 'http://localhost:3000/auth/callback,https://robolearn.github.io/auth/callback', -- Comma-separated: dev + prod URLs |
| 43 | + 'public', |
| 44 | + false, |
| 45 | + '{"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"]}', |
| 46 | + NOW(), |
| 47 | + NOW() |
| 48 | +) |
| 49 | +ON CONFLICT (client_id) DO UPDATE SET |
| 50 | + name = EXCLUDED.name, |
| 51 | + redirect_urls = EXCLUDED.redirect_urls, |
| 52 | + type = EXCLUDED.type, |
| 53 | + disabled = EXCLUDED.disabled, |
| 54 | + metadata = EXCLUDED.metadata, |
| 55 | + updated_at = NOW(); |
| 56 | +``` |
| 57 | + |
| 58 | +**Option 2: Use the seed script** |
| 59 | + |
| 60 | +If you have `DATABASE_URL` set in `.env.local`: |
| 61 | + |
| 62 | +```bash |
| 63 | +npx tsx scripts/seed-public-client.ts |
| 64 | +``` |
| 65 | + |
| 66 | +**Option 3: Use the API endpoint (admin only)** |
| 67 | + |
| 68 | +If you're logged in as admin: |
| 69 | + |
| 70 | +```bash |
| 71 | +curl -X POST http://localhost:3001/api/admin/seed-public-client \ |
| 72 | + -H "Cookie: your-session-cookie" |
| 73 | +``` |
| 74 | + |
| 75 | +## Environment Variables |
| 76 | + |
| 77 | +```env |
| 78 | +# Required |
| 79 | +DATABASE_URL=postgresql://user:pass@host.neon.tech/db?sslmode=require |
| 80 | +BETTER_AUTH_SECRET=your-32-char-secret # openssl rand -base64 32 |
| 81 | +BETTER_AUTH_URL=http://localhost:3001 |
| 82 | +ALLOWED_ORIGINS=http://localhost:3000 |
| 83 | +NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3001 |
| 84 | +
|
| 85 | +# Optional: Email verification |
| 86 | +# Option 1: Resend (free tier: 100/day) |
| 87 | +# RESEND_API_KEY=re_xxxxxxxxx |
| 88 | +# RESEND_FROM_EMAIL=onboarding@resend.dev |
| 89 | +
|
| 90 | +# Option 2: SMTP (Gmail, custom SMTP, etc.) |
| 91 | +# SMTP_HOST=smtp.gmail.com |
| 92 | +# SMTP_PORT=587 |
| 93 | +# SMTP_USER=your@gmail.com |
| 94 | +# SMTP_PASS=app-password |
| 95 | +# SMTP_FROM=your@gmail.com |
| 96 | +# EMAIL_FROM=your@gmail.com |
| 97 | +
|
| 98 | +# Optional: Production OAuth client callback URL |
| 99 | +# ROBOLEARN_INTERFACE_CALLBACK_URL=https://yourdomain.com/auth/callback |
| 100 | +``` |
| 101 | + |
| 102 | +## Features |
| 103 | + |
| 104 | +- OAuth 2.1 with PKCE (no client secrets in browser) |
| 105 | +- JWKS (JSON Web Key Set) for client-side token verification |
| 106 | +- Dynamic client registration (`POST /api/auth/oauth2/register`) |
| 107 | +- Optional email verification via Resend or SMTP |
| 108 | +- Role-based access (admin/user) |
| 109 | +- 7-day sessions with auto-refresh |
| 110 | + |
| 111 | +## Endpoints |
| 112 | + |
| 113 | +| Endpoint | Description | |
| 114 | +|----------|-------------| |
| 115 | +| `/.well-known/openid-configuration` | OIDC discovery document | |
| 116 | +| `/api/auth/jwks` | JWKS public keys for token verification | |
| 117 | +| `/api/auth/oauth2/authorize` | Start OAuth flow | |
| 118 | +| `/api/auth/oauth2/token` | Exchange code for tokens | |
| 119 | +| `/api/auth/oauth2/userinfo` | Get user info | |
| 120 | +| `/api/auth/oauth2/register` | Register new OAuth client | |
| 121 | + |
| 122 | +## Register New OAuth Client |
| 123 | + |
| 124 | +```bash |
| 125 | +curl -X POST http://localhost:3001/api/auth/oauth2/register \ |
| 126 | + -H "Content-Type: application/json" \ |
| 127 | + -d '{ |
| 128 | + "client_name": "My App", |
| 129 | + "redirect_uris": ["http://localhost:4000/callback"], |
| 130 | + "token_endpoint_auth_method": "none" |
| 131 | + }' |
| 132 | +``` |
| 133 | + |
| 134 | +Returns `client_id` to use in your OAuth flow. |
| 135 | + |
| 136 | +## Create Admin User |
| 137 | + |
| 138 | +```sql |
| 139 | +-- After signing up via UI: |
| 140 | +UPDATE "user" SET role = 'admin' WHERE email = 'you@example.com'; |
| 141 | +``` |
| 142 | + |
| 143 | +## Deploy to Vercel |
| 144 | + |
| 145 | +1. Push to GitHub |
| 146 | +2. Import in Vercel, set root to `auth-server` |
| 147 | +3. Add environment variables |
| 148 | +4. Deploy |
0 commit comments