aethex-forge/docs/FOUNDATION-OAUTH-IMPLEMENTATION.md
2025-11-17 02:27:28 +00:00

627 lines
15 KiB
Markdown

# Foundation OAuth Implementation - Complete Guide
## Overview
This guide details the **Phase 3 Switchover** implementation where aethex.dev becomes an OAuth **client** of aethex.foundation. aethex.foundation is the authoritative identity provider (the "issuer" of the Passport).
**Status:** ✅ Implementation complete with PKCE support
---
## Architecture
```
User Flow:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. User visits aethex.dev/login │
│ 2. Clicks "Login with Foundation" │
│ 3. Redirected to aethex.foundation /api/oauth/authorize │
│ 4. User authenticates on Foundation │
│ 5. Foundation redirects back to aethex.dev/auth/callback │
│ 6. Backend exchanges code for access token │
│ 7. User profile synced from Foundation to Corp DB │
│ 8. Session established on aethex.dev │
│ 9. Redirected to dashboard │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Foundation OAuth Credentials
These credentials were obtained during Foundation Phase 1 setup:
```
Foundation URL: https://aethex.foundation
Client ID: aethex_corp
Client Secret: bcoEtyQVGr6Z4557658eUXpDF5FDni2TGNahH3HT-FtylNrLCYwydwLO0sbKVHtfYUnZc4flAODa4BXkzxD_qg
Scopes: openid profile email achievements projects
```
**Environment Variables (add to .env):**
```bash
# Foundation identity provider
VITE_FOUNDATION_URL=https://aethex.foundation
# OAuth client credentials
FOUNDATION_OAUTH_CLIENT_ID=aethex_corp
FOUNDATION_OAUTH_CLIENT_SECRET=bcoEtyQVGr6Z4557658eUXpDF5FDni2TGNahH3HT-FtylNrLCYwydwLO0sbKVHtfYUnZc4flAODa4BXkzxD_qg
```
---
## Foundation OAuth Endpoints
### 1. Authorization Endpoint
Redirect users here to initiate authentication.
```
GET https://aethex.foundation/api/oauth/authorize
?client_id=aethex_corp
&redirect_uri=https://aethex.dev/auth/callback
&response_type=code
&scope=openid profile email achievements projects
&state=<csrf_token>
&code_challenge=<pkce_challenge>
&code_challenge_method=S256
```
**Parameters:**
- `client_id` - aethex_corp (identifies this app)
- `redirect_uri` - Where Foundation redirects after auth
- `response_type` - Always "code" (OAuth 2.0 authorization code flow)
- `scope` - Requested permissions
- `state` - CSRF token (generated by client, validated on callback)
- `code_challenge` - PKCE challenge (SHA256 hash of verifier)
- `code_challenge_method` - S256 (SHA256 PKCE method)
**Implementation:**
See `code/client/lib/foundation-oauth.ts``initiateFoundationLogin()`
### 2. Token Exchange Endpoint
Backend exchanges authorization code for access token.
```
POST https://aethex.foundation/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<auth_code_from_callback>
&redirect_uri=https://aethex.dev/auth/callback
&client_id=aethex_corp
&client_secret=<stored_securely_on_backend>
```
**Response:**
```json
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email achievements projects"
}
```
**Implementation:**
See `code/api/auth/callback.ts``performTokenExchange()`
### 3. User Info Endpoint
Fetch authenticated user's profile.
```
GET https://aethex.foundation/api/oauth/userinfo
Authorization: Bearer <access_token>
Response:
{
"id": "uuid",
"email": "user@example.com",
"username": "username",
"full_name": "Full Name",
"avatar_url": "https://...",
"profile_complete": true,
"achievements": ["achievement_id"],
"projects": ["project_id"]
}
```
**Implementation:**
See `code/api/auth/callback.ts``fetchUserInfoFromFoundation()`
---
## PKCE Implementation
**PKCE** (Proof Key for Code Exchange) adds extra security to OAuth flows.
### How PKCE Works
1. **Client generates code verifier:**
```javascript
verifier = randomString(64 chars, URL-safe)
// Example: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
```
2. **Client generates code challenge:**
```javascript
challenge = base64url(SHA256(verifier));
```
3. **Client sends challenge with authorization request:**
```
GET /api/oauth/authorize?...&code_challenge=...&code_challenge_method=S256
```
4. **Server stores challenge, validates with verifier on token exchange:**
```
POST /api/oauth/token?...&code_verifier=...
```
5. **Server verifies:**
```
base64url(SHA256(submitted_verifier)) === stored_challenge
```
**Why PKCE?**
- Prevents authorization code interception attacks
- Secure for mobile apps and single-page applications
- Required by OAuth 2.1 best practices
**Implementation:**
See `code/client/lib/foundation-oauth.ts``generatePKCEParams()`
---
## Implementation Details
### Frontend Components
#### 1. Login Page (`code/client/pages/Login.tsx`)
Updated to show Foundation OAuth button:
```typescript
<Button onClick={() => initiateFoundationLogin()}>
<Shield /> Login with Foundation
</Button>
```
Features:
- Initiates Foundation OAuth flow
- Generates PKCE parameters
- Stores verifier and state in sessionStorage
- Redirects to Foundation authorization endpoint
#### 2. Foundation OAuth Library (`code/client/lib/foundation-oauth.ts`)
Core OAuth functionality:
```typescript
// Generate PKCE parameters
async function generatePKCEParams(): Promise<{ verifier; challenge }>;
// Build authorization URL
async function getFoundationAuthorizationUrl(options?): Promise<string>;
// Initiate login
async function initiateFoundationLogin(redirectTo?: string): Promise<void>;
// Exchange code for token (called from backend)
async function exchangeCodeForToken(code: string): Promise<TokenResponse>;
```
#### 3. useFoundationAuth Hook (`code/client/hooks/use-foundation-auth.ts`)
Handles OAuth callback:
```typescript
// Process OAuth callback in URL
useFoundationAuth(): UseFoundationAuthReturn
// Check authentication status
useFoundationAuthStatus(): { isAuthenticated, userId }
```
### Backend Endpoints
#### 1. Callback Handler (`code/api/auth/callback.ts`)
**Route:** `GET /auth/callback?code=...&state=...`
**Flow:**
1. Receive authorization code from Foundation
2. Validate state token (CSRF protection)
3. Exchange code for access token
4. Fetch user info from Foundation
5. Sync user to local database
6. Set session cookies
7. Redirect to dashboard
**Code:**
```typescript
async function handleCallback(req, res) {
// 1. Get code from URL
const { code, state } = req.query;
// 2. Validate state
validateState(state);
// 3. Exchange for token
const token = await performTokenExchange(code);
// 4. Fetch user info
const user = await fetchUserInfoFromFoundation(token);
// 5. Sync to database
await syncUserToLocalDatabase(user);
// 6. Set cookies
res.setHeader("Set-Cookie", [
`foundation_access_token=${token}; ...`,
`auth_user_id=${user.id}; ...`,
]);
// 7. Redirect
return res.redirect("/dashboard");
}
```
#### 2. Token Exchange (`POST /auth/callback/exchange`)
**Called from frontend** to exchange code for token safely:
```typescript
async function handleTokenExchange(req, res) {
const { code } = req.body;
// Exchange code with Foundation
// Fetch user info
// Sync to database
// Set cookies
// Return token + user data
}
```
---
## Session Management
### Session Cookies
After successful authentication:
```
Cookies set:
├── foundation_access_token: <jwt_token>
│ └── HttpOnly, Secure, SameSite=Strict
│ └── Max-Age: from expires_in
└── auth_user_id: <user_uuid>
└── Secure, SameSite=Strict
└── Max-Age: 30 days
```
### Using Access Token
For authenticated API requests:
```typescript
// Get token from cookie
const token = document.cookie
.split(";")
.find((c) => c.trim().startsWith("foundation_access_token="))
?.split("=")[1];
// Use in requests
fetch("/api/user/profile", {
headers: {
Authorization: `Bearer ${token}`,
},
credentials: "include",
});
```
### Clearing Session
On logout:
```typescript
// Clear cookies
document.cookie =
"foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
document.cookie = "auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
// Optional: Notify Foundation of logout
await fetch(`${FOUNDATION_URL}/api/oauth/logout`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
});
```
---
## User Profile Sync
### Sync Flow
```
Foundation User Data:
├── id: UUID
├── email: string
├── username: string
├── full_name: string
├── avatar_url: URL
├── profile_complete: boolean
└── achievements, projects: arrays
↓ (via /api/oauth/userinfo)
Corp Local Database (user_profiles table):
├── id: UUID (primary key)
├── email: string
├── username: string
├── full_name: string
├── avatar_url: string
├── profile_completed: boolean
├── updated_at: timestamp
└── (other corp-specific fields)
```
### Upsert Logic
```typescript
await supabase.from("user_profiles").upsert({
id: foundationUser.id, // Match by ID
email: foundationUser.email,
username: foundationUser.username,
full_name: foundationUser.full_name,
avatar_url: foundationUser.avatar_url,
profile_completed: foundationUser.profile_complete,
updated_at: new Date().toISOString(),
});
```
**Note:** Existing user data is preserved, Foundation data is merged/updated.
---
## Testing
### Local Testing
1. **Set up environment:**
```bash
export VITE_FOUNDATION_URL=http://localhost:3001 # Or staging URL
export FOUNDATION_OAUTH_CLIENT_ID=aethex_corp
export FOUNDATION_OAUTH_CLIENT_SECRET=<secret>
```
2. **Test flow:**
- Visit `http://localhost:5173/login`
- Click "Login with Foundation"
- Should redirect to Foundation auth page
- After auth, should return to aethex.dev/auth/callback with code
- Should exchange code and redirect to dashboard
- Check cookies: `foundation_access_token` and `auth_user_id`
3. **Verify database:**
```sql
SELECT id, email, username FROM user_profiles WHERE id = '<user_id>';
```
### Error Scenarios
**Invalid code:**
```
GET /auth/callback?code=invalid
→ 400 "Token exchange failed"
→ Redirect to /login?error=auth_failed
```
**Invalid state:**
```
GET /auth/callback?code=...&state=wrong_state
→ Error "Invalid state token - possible CSRF attack"
→ Redirect to /login?error=auth_failed
```
**Foundation down:**
```
POST /api/oauth/token → ECONNREFUSED
→ Error "Failed to exchange code"
→ Redirect to /login?error=auth_failed
```
---
## Files Modified/Created
### New Files
```
code/
├── client/
│ ├── lib/foundation-oauth.ts (PKCE + OAuth flow)
│ ├── lib/foundation-auth.ts (Token/profile management)
│ └── hooks/use-foundation-auth.ts (React hooks for callback)
├── api/
│ └── auth/callback.ts (OAuth callback handler)
└── .env.foundation-oauth.example (Configuration template)
```
### Modified Files
```
code/
└── client/
└── pages/Login.tsx (Added Foundation button)
```
### Deprecated Files
These can be removed after testing completes:
```
code/api/discord/oauth/start.ts
code/api/discord/oauth/callback.ts
code/api/discord/link.ts
code/api/discord/create-linking-session.ts
code/api/discord/verify-code.ts
```
---
## Deployment Checklist
- [ ] Environment variables set in deployment platform
- [ ] `FOUNDATION_OAUTH_CLIENT_SECRET` stored securely (not in git)
- [ ] `/auth/callback` route properly configured
- [ ] HTTPS enabled (required for secure cookies)
- [ ] SameSite cookie policies enforced
- [ ] PKCE validation working on Foundation side
- [ ] User profile sync tested
- [ ] Session management tested
- [ ] Error handling tested
- [ ] Old Discord OAuth endpoints disabled (after rollout)
---
## Monitoring
### Key Metrics
1. **Auth Success Rate**
- Target: >99%
- Alert if: <95%
2. **Token Exchange Time**
- Target: <500ms
- Alert if: >2s
3. **Error Categories**
- Track: invalid_code, state_mismatch, timeout, etc.
4. **Foundation Connectivity**
- Monitor: /api/oauth/authorize, /api/oauth/token availability
- Alert on: persistent errors from Foundation
### Logging
Key points to log:
```
[Foundation OAuth] Step 1: Received authorization code
[Foundation OAuth] Step 2: State validation passed
[Foundation OAuth] Step 3: Token exchange initiated
[Foundation OAuth] Step 4: User info fetched
[Foundation OAuth] Step 5: User profile synced
[Foundation OAuth] Step 6: Session established
```
---
## Troubleshooting
### Problem: "No authorization code received"
**Cause:** Foundation didn't redirect back properly
**Solutions:**
1. Check `redirect_uri` matches exactly (case-sensitive)
2. Verify Foundation OAuth settings
3. Check browser console for JavaScript errors
### Problem: "Invalid state token"
**Cause:** CSRF validation failed
**Solutions:**
1. Check that state is generated consistently
2. Verify sessionStorage isn't cleared between redirect
3. Check for multiple browser tabs (different state per tab)
### Problem: "Token exchange failed"
**Cause:** Foundation token endpoint unavailable or code invalid
**Solutions:**
1. Check Foundation is running and `/api/oauth/token` accessible
2. Verify `client_id` and `client_secret` are correct
3. Check code hasn't expired (usually 10 minutes)
4. Review Foundation logs for errors
### Problem: User not synced to database
**Cause:** Database error during sync
**Solutions:**
1. Check `user_profiles` table exists and has proper schema
2. Verify Supabase connection and permissions
3. Check user_id isn't duplicated in database
4. Review application logs for sync errors
---
## FAQ
**Q: Why PKCE?**
A: OAuth 2.1 best practice. Prevents interception attacks on the authorization code.
**Q: Can I use Foundation login on multiple apps?**
A: Yes! Any app with valid credentials can use Foundation for auth.
**Q: What if Foundation is down?**
A: Users cannot login. Have a maintenance page ready.
**Q: Do I need to handle token refresh?**
A: Foundation tokens are long-lived. Implement refresh if tokens are short-lived.
**Q: Can users logout from Foundation and still access Corp?**
A: Yes, they have separate sessions. Eventually their Corp token will expire.
**Q: What happens to existing Discord connections?**
A: They're managed by Foundation now, not Corp. Users reconnect on Foundation.
---
## Summary
**aethex.dev is now an OAuth client of aethex.foundation**
- **Foundation** = Single source of truth for identity (Discord, email, etc.)
- **aethex.dev** = OAuth client that redirects to Foundation for authentication
- **User experience** = Seamless login with unified account across ecosystem
- **Security** = PKCE prevents interception, secure cookie handling
- **Architecture** = Axiom Model achieved - Foundation controls all identities
---
**Status:** ✅ Implementation complete, ready for testing and deployment