78.1 Auth Sync Implementation
Documentation for Sessions
78.1 Authentication Sync Implementation
Session: 78 Focus: Chrome Extension Auth Sync from Main App Status: Completed
Problem Statement
The Chrome extension was showing "Not Authenticated" because:
- The main app (
localhost:5200) stores auth tokens inlocalStorage - The extension expected tokens in
chrome.storage.local - No mechanism existed to sync tokens between these storage systems
The extension's content script would send null tokens to the embedded panel, resulting in unauthenticated requests.
Solution Architecture
Overview
┌─────────────────────────────────────────────────────────────────────┐
│ Main App (localhost:5200) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ User logs in → Token stored in localStorage │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
Background Script detects main app tab
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Service Worker (background/service-worker.js) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Injects syncAuthFromMainApp() function into page │ │
│ │ Receives AUTH_SYNC message with token │ │
│ │ Stores token in chrome.storage.local │ │
│ │ Broadcasts AUTH_UPDATED to all tabs │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Content Script (content/inject.js) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Listens for AUTH_UPDATED message │ │
│ │ Reads token from chrome.storage.local │ │
│ │ Sends AI_AUTH_INIT postMessage to embedded panel iframe │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Panel (localhost:5300/panel) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Receives AI_AUTH_INIT via postMessage │ │
│ │ Shows "Authenticated" status │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Implementation Details
1. Background Script: Auth Sync Detection
The service worker monitors tab updates to detect when the user is on the main app:
// apps/gograb-ai-extension/background/service-worker.js
// Session 78: Auth sync from main app
const MAIN_APP_ORIGINS = [
'http://localhost:5200',
'https://gograbspace.com',
'https://app.gograbspace.com'
];
// Session 78: Listen for tab updates to sync auth from main app
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' && tab.url) {
const isMainApp = MAIN_APP_ORIGINS.some(origin => tab.url.startsWith(origin));
if (isMainApp) {
try {
await chrome.scripting.executeScript({
target: { tabId },
func: syncAuthFromMainApp
});
console.log('[GoGrab AI] Auth sync script injected into main app tab');
} catch (e) {
console.log('[GoGrab AI] Could not inject auth sync script:', e.message);
}
}
}
});
2. Auth Extraction Function
This function is injected into main app pages to extract auth from localStorage:
// Session 78: Function injected into main app page to read auth
function syncAuthFromMainApp() {
// Try multiple possible localStorage keys for auth
const token = localStorage.getItem('authToken') ||
localStorage.getItem('token') ||
localStorage.getItem('access_token') ||
localStorage.getItem('gograb_auth_token');
const userId = localStorage.getItem('userId') ||
localStorage.getItem('user_id') ||
localStorage.getItem('gograb_user_id');
const userName = localStorage.getItem('userName') ||
localStorage.getItem('user_name') ||
localStorage.getItem('gograb_user_name');
const userEmail = localStorage.getItem('userEmail') ||
localStorage.getItem('user_email') ||
localStorage.getItem('gograb_user_email');
if (token) {
chrome.runtime.sendMessage({
type: 'AUTH_SYNC',
payload: { token, userId, userName, userEmail }
});
}
}
3. Message Handlers in Service Worker
Handle auth-related messages from content scripts and popup:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.type) {
// Session 78: Auth sync from main app
case 'AUTH_SYNC':
if (message.payload) {
chrome.storage.local.set({
authToken: message.payload.token,
userId: message.payload.userId,
userName: message.payload.userName,
userEmail: message.payload.userEmail,
authSyncedAt: Date.now()
}, () => {
console.log('[GoGrab AI] Auth synced from main app');
// Notify all tabs that auth is updated
broadcastToTabs({ type: 'AUTH_UPDATED' });
sendResponse({ success: true });
});
} else {
sendResponse({ success: false, error: 'No auth payload' });
}
return true; // Keep channel open for async response
// Session 78: Get current auth status
case 'GET_AUTH_STATUS':
chrome.storage.local.get(['authToken', 'userId', 'userName', 'userEmail', 'authSyncedAt'], (result) => {
sendResponse({
authenticated: !!result.authToken,
userId: result.userId,
userName: result.userName,
userEmail: result.userEmail,
syncedAt: result.authSyncedAt
});
});
return true;
// Session 78: Clear auth (logout)
case 'CLEAR_AUTH':
chrome.storage.local.remove(['authToken', 'userId', 'userName', 'userEmail', 'authSyncedAt'], () => {
console.log('[GoGrab AI] Auth cleared');
broadcastToTabs({ type: 'AUTH_UPDATED' });
sendResponse({ success: true });
});
return true;
// Session 78: Request auth sync (manually trigger sync)
case 'REQUEST_AUTH_SYNC':
requestAuthSyncFromMainApp().then((success) => {
sendResponse({ success });
});
return true;
// ... other handlers
}
});
4. Content Script: Auth to Panel Bridge
The content script listens for auth updates and forwards them to the embedded panel:
// apps/gograb-ai-extension/content/inject.js
// Bridge Protocol Constants (Session 75.7)
const BRIDGE_MESSAGES = {
// Auth
AI_AUTH_INIT: 'AI_AUTH_INIT',
AI_AUTH_REFRESH: 'AI_AUTH_REFRESH',
// ... other messages
};
// Session 78: Listen for auth updates from background
case 'AUTH_UPDATED':
console.log('[GoGrab AI Extension] Auth updated, refreshing panel auth');
sendAuthToPanel();
sendResponse({ success: true });
break;
/**
* Send auth to panel if available
* Session 78: Enhanced with retry logic and auth sync request
*/
function sendAuthToPanel(retryCount = 0) {
chrome.storage.local.get(['authToken', 'userId', 'userName', 'userEmail'], (result) => {
if (result.authToken) {
sendBridgeMessage(BRIDGE_MESSAGES.AI_AUTH_INIT, {
token: result.authToken,
userId: result.userId,
userName: result.userName,
userEmail: result.userEmail,
platform: 'chrome-extension',
instanceId: instanceId,
});
console.log('[GoGrab AI Extension] Auth sent to panel');
} else if (retryCount < 3) {
// No auth found - request sync from main app and retry
console.log('[GoGrab AI Extension] No auth found, requesting sync (attempt', retryCount + 1, ')');
chrome.runtime.sendMessage({ type: 'REQUEST_AUTH_SYNC' }, (response) => {
if (response?.success) {
// Wait a bit for the sync to complete then retry
setTimeout(() => sendAuthToPanel(retryCount + 1), 1000);
} else {
console.log('[GoGrab AI Extension] Auth sync failed, no main app tab found');
}
});
} else {
console.log('[GoGrab AI Extension] No auth available after retries');
}
});
}
5. Popup UI: Login and Sync Buttons
Added fallback controls in the popup for manual auth management:
// apps/gograb-ai-extension/popup/popup.js
// Session 78: Auth DOM Elements
const authIndicator = document.getElementById('authIndicator');
const authStatusText = document.getElementById('authStatusText');
const authUserInfo = document.getElementById('authUserInfo');
const authUserName = document.getElementById('authUserName');
const authUserEmail = document.getElementById('authUserEmail');
const loginBtn = document.getElementById('loginBtn');
const syncAuthBtn = document.getElementById('syncAuthBtn');
// Session 78: Main app URLs for login
const MAIN_APP_LOGIN_URL = 'http://localhost:5200/auth/login';
// Session 78: Check authentication status
async function checkAuthStatus() {
try {
const status = await chrome.runtime.sendMessage({ type: 'GET_AUTH_STATUS' });
updateAuthUI(status);
} catch (err) {
console.error('[Auth] Failed to get status:', err);
updateAuthUI({ authenticated: false });
}
}
// Session 78: Update auth UI based on status
function updateAuthUI(status) {
if (status.authenticated) {
authIndicator.classList.add('authenticated');
authStatusText.textContent = 'Authenticated';
authStatusText.style.color = '#22c55e';
// Show user info
if (status.userName || status.userEmail) {
authUserInfo.style.display = 'block';
authUserName.textContent = status.userName || 'User';
authUserEmail.textContent = status.userEmail ? ` (${status.userEmail})` : '';
}
// Hide login, show sync button
loginBtn.style.display = 'none';
syncAuthBtn.style.display = 'block';
syncAuthBtn.textContent = 'Refresh Auth';
} else {
authIndicator.classList.remove('authenticated');
authStatusText.textContent = 'Not Authenticated';
authStatusText.style.color = '#ef4444';
// Hide user info
authUserInfo.style.display = 'none';
// Show login and sync buttons
loginBtn.style.display = 'block';
syncAuthBtn.style.display = 'block';
syncAuthBtn.textContent = 'Sync Auth';
}
}
Key Design Decisions
1. Multiple Storage Key Support
The sync function tries multiple localStorage key names to maximize compatibility:
const token = localStorage.getItem('authToken') ||
localStorage.getItem('token') ||
localStorage.getItem('access_token') ||
localStorage.getItem('gograb_auth_token');
Reasoning: Different versions of the main app might use different key names. This ensures backward and forward compatibility.
2. Retry Logic with Exponential Backoff
The content script retries auth sync up to 3 times if no token is found:
if (retryCount < 3) {
chrome.runtime.sendMessage({ type: 'REQUEST_AUTH_SYNC' }, (response) => {
if (response?.success) {
setTimeout(() => sendAuthToPanel(retryCount + 1), 1000);
}
});
}
Reasoning: The main app tab might not be immediately available, or the auth sync might take time to complete.
3. Broadcast Auth Updates
When auth is synced, all tabs are notified:
broadcastToTabs({ type: 'AUTH_UPDATED' });
Reasoning: Multiple extension panels might be open across different tabs. All should be updated when auth changes.
Testing
Manual Testing Steps
Fresh Install Test:
- Install extension
- Open popup → Should show "Not Authenticated"
- Open main app → Login
- Extension should auto-sync → Popup shows "Authenticated"
Sync Button Test:
- While logged in to main app
- Click "Sync Auth" in popup
- Should update auth status
Panel Auth Test:
- Open any webpage
- Click extension floating button
- Panel should show "Authenticated" status
Future Improvements
- Token Refresh: Implement automatic token refresh before expiry
- Logout Sync: Detect logout in main app and clear extension auth
- Multiple Accounts: Support switching between accounts
- Token Encryption: Encrypt stored tokens in chrome.storage