FamousAI Auth Implementation Guide
Complete copy-paste solution to fix redirect loops
Current Problem: Infinite Redirect Loop
1. User clicks "Play Scrabble" on SnappyIdioms.com
2. Redirected to: play.snappyidioms.com/?auth=TOKEN
3. FamousAI doesn't validate the token
4. FamousAI redirects back to SnappyIdioms.com
5. ♾️ INFINITE LOOP
Solution: Validate JWT Token
✅ Read ?auth=TOKEN from URL on page load
✅ Validate with SnappyIdioms API
✅ Store user data in localStorage
✅ Start game immediately - NO redirects
✅ Clean URL (remove ?auth=)
Step 1: Client-Side Auth (snappyAuth.js)
// ============================================
// FAMOUS.AI CLIENT-SIDE AUTH IMPLEMENTATION
// File: src/auth/snappyAuth.js
// ============================================
/**
* CRITICAL: This must run BEFORE any other auth checks
* Call this in your App.js or main game component useEffect
*/
export async function initSnappyAuth() {
console.log('[SNAPPY-AUTH] Initializing...');
// Step 1: Check if JWT token exists in URL
const urlParams = new URLSearchParams(window.location.search);
const jwtToken = urlParams.get('auth');
if (!jwtToken) {
console.log('[SNAPPY-AUTH] No JWT token in URL');
return { authenticated: false, reason: 'no_token' };
}
console.log('[SNAPPY-AUTH] JWT token found, validating...');
try {
// Step 2: Validate token with SnappyIdioms backend
const response = await fetch('https://snappyidioms.com/functions/verifyGameToken', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: jwtToken })
});
const result = await response.json();
if (result.valid) {
console.log('[SNAPPY-AUTH] ✅ Token valid for user:', result.email);
// Step 3: Store user data in localStorage
localStorage.setItem('snappy_platform_token', jwtToken);
localStorage.setItem('snappy_username', result.name);
localStorage.setItem('snappy_user_email', result.email);
localStorage.setItem('snappy_is_premium', result.isPremium);
localStorage.setItem('snappy_is_guest', 'false');
localStorage.setItem('snappy_auth_timestamp', Date.now().toString());
// Step 4: Clean URL (remove ?auth=token)
const cleanUrl = window.location.origin + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
return {
authenticated: true,
user: {
email: result.email,
name: result.name,
isPremium: result.isPremium
}
};
} else {
console.error('[SNAPPY-AUTH] ❌ Invalid token:', result.error);
return { authenticated: false, reason: 'invalid_token' };
}
} catch (error) {
console.error('[SNAPPY-AUTH] ❌ Validation failed:', error);
return { authenticated: false, reason: 'validation_error', error };
}
}
/**
* Check if user is authenticated (call this for session checks)
*/
export function isSnappyAuthenticated() {
const token = localStorage.getItem('snappy_platform_token');
const timestamp = localStorage.getItem('snappy_auth_timestamp');
if (!token || !timestamp) return false;
// Token expires after 24 hours
const age = Date.now() - parseInt(timestamp);
if (age > 24 * 60 * 60 * 1000) {
console.log('[SNAPPY-AUTH] Token expired');
clearSnappyAuth();
return false;
}
return true;
}
/**
* Get current user data
*/
export function getSnappyUser() {
return {
email: localStorage.getItem('snappy_user_email'),
name: localStorage.getItem('snappy_username'),
isPremium: localStorage.getItem('snappy_is_premium') === 'true',
isGuest: localStorage.getItem('snappy_is_guest') === 'true'
};
}
/**
* Clear auth data (for logout or reset)
*/
export function clearSnappyAuth() {
localStorage.removeItem('snappy_platform_token');
localStorage.removeItem('snappy_username');
localStorage.removeItem('snappy_user_email');
localStorage.removeItem('snappy_is_premium');
localStorage.removeItem('snappy_is_guest');
localStorage.removeItem('snappy_auth_timestamp');
}Step 2: App.js Integration
// ============================================
// APP.JS INTEGRATION
// ============================================
import React, { useEffect, useState } from 'react';
import { initSnappyAuth, isSnappyAuthenticated, getSnappyUser } from './auth/snappyAuth';
function App() {
const [authState, setAuthState] = useState('checking');
const [user, setUser] = useState(null);
useEffect(() => {
async function handleAuth() {
// CRITICAL: Check JWT token FIRST, before any other auth logic
const result = await initSnappyAuth();
if (result.authenticated) {
setUser(result.user);
setAuthState('authenticated');
return; // STOP HERE - don't show any auth modals
}
// If no JWT, check existing session
if (isSnappyAuthenticated()) {
setUser(getSnappyUser());
setAuthState('authenticated');
return;
}
// No valid auth - show guest mode or sign-in
setAuthState('unauthenticated');
}
handleAuth();
}, []);
if (authState === 'checking') {
return <div>Loading...</div>;
}
if (authState === 'authenticated') {
return <GameComponent user={user} />;
}
return <AuthModal />; // Only show if no JWT and no session
}API Endpoint (Already exists on SnappyIdioms)
FamousAI calls: https://snappyidioms.com/api/verifyGameToken
// ============================================
// SNAPPYIDIOMS BACKEND - verifyGameToken
// File: functions/verifyGameToken
// (Already implemented - FamousAI just needs to call it)
// ============================================
import Deno from 'https://deno.land/x/deno@v1.32.0/mod.ts';
const JWT_SECRET = Deno.env.get('JWT_SECRET');
Deno.serve(async (req) => {
// CORS headers
if (req.method === 'OPTIONS') {
return new Response('ok', {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
});
}
try {
const { token } = await req.json();
// Decode and verify JWT
const [header, payload, signature] = token.split('.');
const decodedPayload = JSON.parse(atob(payload));
// Check expiration
if (Date.now() > decodedPayload.exp) {
return Response.json({ valid: false, error: 'Token expired' });
}
// Verify signature
const isValid = await verifySignature(`${header}.${payload}`, signature, JWT_SECRET);
if (isValid) {
return Response.json({
valid: true,
user: {
email: decodedPayload.email,
name: decodedPayload.name,
isPremium: decodedPayload.isPremium
}
});
}
return Response.json({ valid: false, error: 'Invalid signature' });
} catch (error) {
return Response.json({ valid: false, error: error.message }, { status: 500 });
}
});Implementation Checklist
// ============================================
// IMPLEMENTATION CHECKLIST FOR FAMOUS.AI
// ============================================
✅ STEP 1: Add snappyAuth.js to your project
- Create file: src/auth/snappyAuth.js
- Copy the "Client-Side Auth" code above
✅ STEP 2: Update App.js or main game component
- Import: import { initSnappyAuth } from './auth/snappyAuth'
- Call initSnappyAuth() in useEffect BEFORE any other auth checks
- Do NOT show sign-in modal if authenticated
✅ STEP 3: Test the flow
1. Go to https://snappyidioms.com
2. Sign in as a user
3. Click "Play Scrabble"
4. Should redirect to: https://play.snappyidioms.com/?auth=TOKEN
5. Game should validate token and start immediately
6. NO redirect back to SnappyIdioms
7. When done, user can navigate back manually
✅ STEP 4: Add "Reset Game Data" button (Settings/Footer)
import { clearSnappyAuth } from './auth/snappyAuth';
<button onClick={() => {
if (confirm('Clear all game data?')) {
clearSnappyAuth();
window.location.reload();
}
}}>
Reset Game Data
</button>
✅ STEP 5: Environment Variable (Already set on FamousAI)
- JWT_SECRET must match SnappyIdioms' secret
- Contact Samuel if you don't have it
⚠️ CRITICAL: DO NOT redirect to SnappyIdioms.com when JWT is valid
⚠️ CRITICAL: Remove any code that redirects back when auth succeedsTroubleshooting Loops
// ============================================
// TROUBLESHOOTING INFINITE LOOPS
// ============================================
❌ WRONG: Checking session before JWT
if (!localStorage.getItem('user')) {
window.location.href = 'https://snappyidioms.com'; // CAUSES LOOP
}
✅ CORRECT: Check JWT first
const auth = await initSnappyAuth();
if (auth.authenticated) {
// Start game immediately
return;
}
// Then check local session
if (isSnappyAuthenticated()) {
// Continue playing
return;
}
// Only now show auth modal
❌ WRONG: Always redirecting on any error
if (!token || error) {
window.location.href = 'https://snappyidioms.com'; // CAUSES LOOP
}
✅ CORRECT: Show error in-game
if (!token || error) {
showErrorMessage('Session expired. Please return to SnappyIdioms.');
// DO NOT auto-redirect
}Testing the Fix
- 1. Sign in to snappyidioms.com
- 2. Click "Play Scrabble"
- 3. Should see: play.snappyidioms.com/?auth=TOKEN
- 4. Game validates token and starts playing
- 5. URL cleans to: play.snappyidioms.com
- 6. User plays the game
- 7. When done, user navigates back manually (browser back button)
- 8. ✅ NO INFINITE LOOP