Cross-Suite Auth Debugging Guide
Step-by-step guide for diagnosing and fixing cross-suite SSO issues in the Hello World Co-Op platform. All authenticated suites share sessions via httpOnly cookies on the .helloworlddao.com domain, proxied through oracle-bridge.
Table of Contents
- Troubleshooting Decision Tree
- Section 1: Cookie Domain Verification
- Section 2: Auth-Service Session Validation
- Section 3: Redirect Loop Diagnosis
- Section 4: Logout Propagation Testing
- Section 5: Browser DevTools Inspection
- Real-World Examples from FAS-8.1
- Related Documentation
Troubleshooting Decision Tree
Use this to quickly identify the problem category:
"I cannot log in on any suite" -> Check auth-service canister health (Section 2) -> Check oracle-bridge is running: curl https://staging-oracle.helloworlddao.com/health -> If oracle-bridge is down, all auth-integrated suites will fail
"Login works on Suite A but fails on Suite B" -> Check cookie domain (Section 1) -> The cookie must be set on .helloworlddao.com (with leading dot) -> Without the dot, the cookie is scoped to a single subdomain
"Infinite redirect loop between suite and login page" -> Check ProtectedRoute useCallback dependencies (Section 3) -> Unstable refs cause the auth check to re-trigger on every render
"Logout does not work (still logged in after clicking logout)" -> Check CSRF token in logout request (Section 4) -> Verify server-side cookie clearing on .helloworlddao.com domain
"User appears logged out intermittently" -> Check session expiration and refresh logic -> Check for race conditions in authCookieClient
Section 1: Cookie Domain Verification
How to Check Cookies in DevTools
- Open browser DevTools (F12 or Cmd+Shift+I)
- Go to Application tab -> Cookies -> select the current domain
- Look for the session cookie (usually named
sessionorauth_token)
Expected Cookie Configuration
| Property | Expected Value | Why |
|---|---|---|
| Domain | .helloworlddao.com | Shared across all subdomains |
| SameSite | Lax | Allows navigation between subdomains |
| Secure | Yes (checkmark) | HTTPS only -- prevents MITM |
| HttpOnly | Yes (checkmark) | Not accessible via JavaScript |
| Path | / | Available on all paths |
Common Cookie Issues
Issue: Cookie domain is staging-think-tank.helloworlddao.com (no leading dot)
The cookie is scoped to only the think-tank subdomain. Other suites cannot read it.
Fix: In oracle-bridge, ensure the cookie domain is set with the leading dot:
res.cookie('session', token, {
domain: '.helloworlddao.com', // Leading dot is critical
httpOnly: true,
secure: true,
sameSite: 'lax',
});Issue: Cookie not appearing at all
- Check that oracle-bridge is setting the
Set-Cookieheader (Section 5, Network tab) - Check that the request is going to the correct oracle-bridge URL
- Verify the browser is not blocking third-party cookies
curl Verification
# Test login and check Set-Cookie header
curl -v -X POST https://staging-oracle.helloworlddao.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"testpass"}' \
2>&1 | grep -i "set-cookie"
# Expected output should include:
# Set-Cookie: session=...; Domain=.helloworlddao.com; Path=/; HttpOnly; Secure; SameSite=LaxSection 2: Auth-Service Session Validation
Testing Session Endpoint
# Check if a session is valid
curl -v https://staging-oracle.helloworlddao.com/auth/session \
-H "Cookie: session=YOUR_SESSION_TOKEN_HERE"Expected response (authenticated):
{
"authenticated": true,
"user_id": "abc123-...",
"email": "user@example.com",
"roles": ["member"]
}Expected response (not authenticated):
{
"authenticated": false
}Troubleshooting Session Issues
401 Unauthorized:
- Session token has expired
- Session was invalidated by a logout on another suite
- auth-service canister is not responding
user_id missing from response:
- auth-service returned session data without user details
- Check auth-service canister logs
Health check:
# Check oracle-bridge health
curl https://staging-oracle.helloworlddao.com/health
# Expected: {"status":"ok"}
# Check auth-service canister directly
dfx canister status gexqh-jqaaa-aaaae-acsxq-cai --network icSection 3: Redirect Loop Diagnosis
Symptoms
The browser rapidly alternates between the suite URL and the login page, eventually showing "too many redirects" error.
Root Cause
The ProtectedRoute component checks auth state on every render. If the auth check callback has unstable references (changes on every render), it creates an infinite loop:
- ProtectedRoute checks auth -> not authenticated -> redirect to login
- Login page authenticates -> redirect back to suite
- ProtectedRoute re-renders -> useCallback ref changed -> re-checks auth -> ...
Diagnosis Steps
- Open DevTools Network tab -- look for rapid successive requests to the suite and login page
- Check ProtectedRoute source code -- look for
useCallbackwith unstable dependencies - Test with direct navigation -- navigate directly to a protected route (not via redirect) to rule out redirect URL issues
Fix Pattern
// WRONG -- useCallback with unstable deps
const checkAuth = useCallback(() => {
if (!isAuthenticated) {
navigate(`/login?returnUrl=${encodeURIComponent(location.pathname)}`);
}
}, [isAuthenticated, navigate, location]); // navigate and location change on every render
// CORRECT -- stable ref, use window.location for redirect
const loginUrl = useMemo(
() => `${FOUNDERY_OS_URL}/login?returnUrl=${encodeURIComponent(window.location.href)}`,
[FOUNDERY_OS_URL]
);
useEffect(() => {
if (!isLoading && !isAuthenticated) {
window.location.href = loginUrl;
}
}, [isLoading, isAuthenticated, loginUrl]);Verify returnUrl Encoding
The returnUrl parameter must be properly URL-encoded:
# Test returnUrl encoding
# Good: returnUrl=https%3A%2F%2Fstaging-portal.helloworlddao.com%2Fdashboard
# Bad: returnUrl=https://staging-portal.helloworlddao.com/dashboard (unencoded)Section 4: Logout Propagation Testing
Full Logout Flow Test
# Step 1: Login and get session cookie
SESSION=$(curl -s -c - -X POST https://staging-oracle.helloworlddao.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"testpass"}' \
| grep session | awk '{print $NF}')
echo "Session: $SESSION"
# Step 2: Verify session is valid
curl -s https://staging-oracle.helloworlddao.com/auth/session \
-H "Cookie: session=$SESSION"
# Expected: {"authenticated":true,...}
# Step 3: Logout (include CSRF token if required)
curl -s -X POST https://staging-oracle.helloworlddao.com/auth/logout \
-H "Cookie: session=$SESSION"
# Step 4: Verify session is now invalid
curl -s https://staging-oracle.helloworlddao.com/auth/session \
-H "Cookie: session=$SESSION"
# Expected: {"authenticated":false}What to Check
- After logout: The session cookie should be cleared on
.helloworlddao.comdomain (server setsSet-CookiewithMax-Age=0) - Cross-suite: After logging out on Suite A, Suite B should also show as logged out (because the same session cookie is invalidated server-side)
- CSRF token: Logout requests may require a CSRF token. Check the oracle-bridge implementation for the exact requirement.
Common Logout Issues
Issue: Logout on Suite A, still logged in on Suite B
- Server-side session was not invalidated (cookie is still valid)
- Suite B cached the auth state in localStorage and is not re-checking
Fix: Ensure logout invalidates the session server-side AND clears the client-side localStorage:
// In logout handler
await fetch(`${ORACLE_BRIDGE_URL}/auth/logout`, { method: 'POST', credentials: 'include' });
localStorage.removeItem('user_data');Section 5: Browser DevTools Inspection
Network Tab: Check Set-Cookie Headers
- Open DevTools -> Network tab
- Filter by the oracle-bridge domain
- Click on the login/session request
- Check Response Headers for
Set-Cookie
Expected:
Set-Cookie: session=abc123...; Domain=.helloworlddao.com; Path=/; HttpOnly; Secure; SameSite=LaxRed flags:
- Missing
Domainattribute (cookie scoped to request domain only) Domainwithout leading dot (e.g.,helloworlddao.cominstead of.helloworlddao.com)- Missing
Secureflag (cookie will not be sent over HTTPS) SameSite=Strict(prevents cross-subdomain cookie sharing)
Console Tab: Check for Auth Errors
Common console errors related to auth:
| Error | Meaning |
|---|---|
Failed to fetch on /auth/session | oracle-bridge unreachable or CORS issue |
401 Unauthorized | Session expired or invalid |
TypeError: Cannot read property 'user_id' | Session response missing expected fields |
ERR_ERL_KEY_GEN_IPV6 | oracle-bridge rate limiter IPv6 bug (fixed in FAS-8.1) |
Application Tab: Verify localStorage Auth State
For suites using the bridge pattern (think-tank, dao, dao-admin):
- Go to Application -> Local Storage -> select the suite domain
- Check for
user_datakey - Verify it contains valid user information (user_id, email, roles)
If user_data is empty but the session cookie exists, the authCookieClient bridge is not running correctly.
Real-World Examples from FAS-8.1
These bugs were discovered and fixed during FAS-8.1 staging verification:
Bug 1: oracle-bridge Rate Limiter IPv6 (Critical)
Symptom: oracle-bridge crashed on startup with ERR_ERL_KEY_GEN_IPV6. Root cause: express-rate-limit tried to generate rate limit keys from IPv6 addresses, which failed validation. Fix: Updated rate limiter configuration to handle IPv6 addresses correctly.
Bug 2: Missing .env.staging in dao-suite and dao-admin-suite
Symptom: Auth requests failed in staging because VITE_ORACLE_BRIDGE_URL was undefined. Root cause: .env.staging files were not committed to the repositories. Fix: Added .env.staging with correct oracle-bridge URL (https://staging-oracle.helloworlddao.com).
Bug 3: ProtectedRoute Infinite Loop (Critical)
Symptom: dao-suite showed "too many redirects" error. Browser rapidly alternated between portal and think-tank login. Root cause: useCallback in ProtectedRoute had navigate as a dependency, which changed on every render. Fix: Replaced navigate() with window.location.href for stable redirect and memoized the login URL.
Bug 4: Dashboard Cookie-to-localStorage Bridge
Symptom: User logged in via think-tank (cookie set), navigated to dao-suite, but dashboard showed "not authenticated". Root cause: dao-suite components read auth state from localStorage, but the cookie session was not being bridged to localStorage. Fix: Implemented authCookieClient that calls /auth/session on app load and populates localStorage.user_data.
Bug 5: user-service Canister Out of Cycles
Symptom: Login succeeded but user profile data returned empty (no name, no email in dashboard). Root cause: user-service canister had only 0.5 TC remaining and was throttling responses. Fix: Topped up user-service canister with additional cycles via dfx wallet send.
Related Documentation
- Architecture Overview -- Cross-suite auth architecture
- Troubleshooting Guide -- SSO issues section
- Rollback Procedures -- Emergency rollback steps
- Local Setup Guide -- Oracle-bridge local setup