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.
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.
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.
Below is the step-by-step process we followed to implement the SSO solution.
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:
We implemented a content script in the Chrome extension that interacts with the web application's session storage to synchronize authentication data.
// Check every second to see if the user is logged in const interval = setInterval(() => { chrome.storage.local.set({ session: JSON.parse(localStorage.getItem('session')), }); chrome.storage.local.get(['session'], (result) => { if (result.session !== null) { clearInterval(interval); } }); }, 1000);
In this script:
localStorage
.chrome.storage.local
.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.
Implementing single sign-on using NextAuth allowed us to synchronize user authentication between our Next.js web application and the browser extension effectively. This solution streamlined the login process, reducing friction and enhancing user satisfaction. A seamless authentication experience is crucial for user retention, and our approach demonstrates how SSO can be successfully integrated into a serverless Next.js environment with a browser 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.
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.
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.
Below is the step-by-step process we followed to implement the SSO solution.
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:
We implemented a content script in the Chrome extension that interacts with the web application's session storage to synchronize authentication data.
// Check every second to see if the user is logged in const interval = setInterval(() => { chrome.storage.local.set({ session: JSON.parse(localStorage.getItem('session')), }); chrome.storage.local.get(['session'], (result) => { if (result.session !== null) { clearInterval(interval); } }); }, 1000);
In this script:
localStorage
.chrome.storage.local
.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.
Implementing single sign-on using NextAuth allowed us to synchronize user authentication between our Next.js web application and the browser extension effectively. This solution streamlined the login process, reducing friction and enhancing user satisfaction. A seamless authentication experience is crucial for user retention, and our approach demonstrates how SSO can be successfully integrated into a serverless Next.js environment with a browser extension.
Let’s Talk And Get Started