RipeSeed Logo

Managing User Sessions Across A NextJS App And Chrome Extension

August 21, 2023
Learn how we tackled user login issues in a Next.js web application paired with a browser extension by implementing an SSO solution to synchronize user sessions for a seamless experience.
Managing User Sessions Across A NextJS App And Chrome Extension
Nushirvan Naseer
Software Engineer
4 min read

Implementing Single Sign-On (SSO) Between a Next.js App and Chrome Extension

While developing a Next.js web application alongside a browser extension, our team encountered a significant user login issue during testing. Although the initial development went smoothly, this problem negatively impacted the user experience. This post outlines the challenge we faced, our solution involving single sign-on (SSO), and the process we followed to implement it.

The Problem: Disconnected User Sessions

Users who registered and logged into the web application were required to sign in again when using the browser extension. This redundancy was not only inconvenient but also disrupted the seamless experience we aimed to provide. Recognizing the need for a unified authentication process, we set out to implement an SSO solution to synchronize user sessions across both platforms.

Our Solution: Implementing SSO with NextAuth

To address this issue, we utilized NextAuth to create a single sign-on system. The goal was to allow users to log in once on the web application and maintain that session across both the backend and the browser extension. This approach would eliminate the need for repeated authentication, enhancing the overall user experience.

The Implementation Process

Below is the step-by-step process we followed to implement the SSO solution.

Configuring NextAuth for Unified Authentication

We set up NextAuth to handle authentication for both the web app and the browser extension. The signIn callback was customized to manage user sessions effectively.

signIn: async ({ account, user }) => { try { const cookieStore = cookies(); // Check for callbackUrl in production environment let callbackUrl = cookieStore.get('__Secure-next-auth.callback-url')?.value; // If no callbackUrl found, check using development cookie key name if (!callbackUrl) { callbackUrl = cookieStore.get('next-auth.callback-url')?.value; } // Extract and validate the user role const role = callbackUrl?.split('=').pop()?.toUpperCase() as RolesEnum; if (!role?.length || !allowedRoles.includes(role)) { return false; } // Validate the user with the backend const dbUser = await validateUser({ access_token: account?.access_token, id_token: account?.id_token, as: role, }); if (!dbUser.success) { return false; } // Bind additional properties to the user object user.accessToken = dbUser.data.accessToken; user.refreshToken = account?.refresh_token || ''; user.id = dbUser.data.user.id; user.role = dbUser.data.user.role; user.image = dbUser.data.user.image; user.createdAt = dbUser.data.user.createdAt; user.name = dbUser.data.user.name; user.tokenExpires = account?.expires_at || 0; user.googleAccessToken = account?.access_token || ''; return true; } catch (err) { console.log('Error in signIn callback', { err }); return false; } }

In this configuration:

  • Session Management: NextAuth handles the session with Google OAuth, while our backend maintains a session for user authentication.
  • Role Verification: The user's role is extracted from the callback URL and validated against allowed roles.
  • User Validation: The user is validated with the backend using tokens obtained from the authentication provider.
  • Session Persistence: User details and tokens are stored to maintain the session across the web app and extension.

Synchronizing Sessions with the Chrome Extension

We implemented a content script in the Chrome extension that interacts with the web application's session storage to synchronize authentication data.

// content-script.js (function() { 'use strict'; // Function to check and sync session data function syncSessionData() { try { // Access the web app's localStorage const sessionData = localStorage.getItem('next-auth.session-token'); if (sessionData) { // Store session data in Chrome extension storage chrome.storage.local.set({ 'session-token': sessionData, 'last-sync': Date.now() }, function() { console.log('Session data synchronized with extension'); }); // Retrieve additional user data if available const userData = localStorage.getItem('user-data'); if (userData) { chrome.storage.local.set({ 'user-data': userData }); } } } catch (error) { console.error('Error syncing session data:', error); } } // Monitor for changes in localStorage window.addEventListener('storage', function(e) { if (e.key === 'next-auth.session-token') { syncSessionData(); } }); // Initial sync on page load syncSessionData(); // Periodic check every 30 seconds setInterval(syncSessionData, 30000); })();

In this script:

  • Session Monitoring: The script periodically checks if the user is logged in by accessing the web app's localStorage.
  • Data Sharing: Upon detecting an active session, it stores the session data in the Chrome extension's chrome.storage.local.
  • Content Script: This approach leverages the content script's ability to access both the extension's and the web page's storage, enabling seamless data sharing.

Extension Background Script

To utilize the synced session data in the extension, we created a background script:

// background.js chrome.storage.local.get(['session-token', 'user-data'], function(result) { if (result['session-token']) { // User is authenticated const sessionToken = result['session-token']; const userData = result['user-data'] ? JSON.parse(result['user-data']) : null; // Use the session token for API requests fetch('https://your-api.com/endpoint', { headers: { 'Authorization': `Bearer ${sessionToken}`, 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { console.log('API call successful:', data); }) .catch(error => { console.error('API call failed:', error); }); } }); // Listen for storage changes chrome.storage.onChanged.addListener(function(changes, namespace) { if (namespace === 'local' && changes['session-token']) { console.log('Session token updated:', changes['session-token'].newValue); // Handle session token updates } });

Maintaining Session Consistency

By sharing the session data between the web app and the extension, we ensured that the user's authentication state remained consistent. This method eliminated the need for the user to log in separately on the extension, providing a unified and smooth user experience.

RipeSeed - All Rights Reserved ©2025