DEV Community

Cover image for Cookie-based login for Chrome extensions with Supabase
Ahmed Sulaiman
Ahmed Sulaiman

Posted on

Cookie-based login for Chrome extensions with Supabase

I recently built a Chrome extension for Katalog, the audio-first read-it-later app. It's similar to Pocket, but focused on listening to the articles you save. When users find an interesting piece of content, they can save it with the extension. Katalog then parses it and generates optimised audio narration.

I needed the extension to recognize users who were already logged into the web app, without making them log in again. I also wanted to avoid duplicating the authentication flow or storing tokens in multiple places. Here's how I approached it, what worked, and what I'd do differently next time.

1. Why I chose cookie-based auth

I tried a few different approaches before settling on cookie-based authentication. At first, I considered integrating the Supabase SDK directly into the extension, or building a custom login flow inside the extension. Both options felt too heavy and required me to manage tokens separately from the web app.

Instead, I realized I could just check for the presence of the Supabase auth cookies that are set when a user logs into the web app. If those cookies exist, the extension can assume the user is authenticated. This way, I could reuse the existing login flow and avoid building extra UI or logic.

2. Architecture overview

Here’s a simple diagram of how things fit together:

+-------------------+         +-------------------+         +-------------------+
|                   |         |                   |         |                   |
|   Chrome          |  <----> |   Web App         | <-----> |   Supabase        |
|   Extension       |         |   (React, Magic   |         |   (Auth, DB)      |
|                   |         |    Link Auth)     |         |                   |
+-------------------+         +-------------------+         +-------------------+
        |                              ^
        |                              |
        |   (Checks for Supabase       |
        |    cookies on web app        |
        |    domain)                   |
        +------------------------------+
Enter fullscreen mode Exit fullscreen mode
  • The extension checks for Supabase cookies on the web app’s domain.
  • If authenticated, it calls a web app endpoint (e.g., /api/parse-article) to perform actions.
  • The web app handles authentication and talks to Supabase.

3. Setting up Supabase auth in the web app

I use Supabase's Magic Link authentication in the web app that I built with React Router v7 (framework mode). The web app also uses server-side rendering, so I handle authentication with middleware that intercepts requests and checks for Supabase cookies in the headers.

You can set up Supabase authentication for SSR with this guide. The important part is that Supabase sets its own cookies when a user logs in. You don't need to do anything special in the extension to create or manage these cookies.

4. Chrome extension: cookie detection

The core of the authentication check happens in the background script. Here’s the function I use to check for the presence of Supabase’s auth cookies:

// background.ts
async function hasAuthCookies(domain: string): Promise<boolean> {
  const cookies = await browser.cookies.getAll({ domain });
  const authCookies = cookies.filter((cookie) =>
    // These env vars are set to the standard Supabase cookie names
    cookie.name.includes(import.meta.env.VITE_KATALOG_AUTH_TOKEN_COOKIE) ||
    cookie.name.includes(import.meta.env.VITE_KATALOG_AUTH_TOKEN_CODE_VERIFIER_COOKIE)
  );
  return authCookies.length > 0;
}
Enter fullscreen mode Exit fullscreen mode

I rely on environment variables for the cookie names, but these are just the defaults from Supabase. You can inspect these cookies in the browser's storage after logging in. Since I'm using Vite Web Extension plugin, I store these variables in a .env file. Vite automatically loads any environment variables prefixed with VITE_, making them available through import.meta.env in your code.

# .env
VITE_KATALOG_AUTH_TOKEN_COOKIE=sb-access-token
VITE_KATALOG_AUTH_TOKEN_CODE_VERIFIER_COOKIE=sb-refresh-token
Enter fullscreen mode Exit fullscreen mode

5. Manifest and permissions

Here’s the relevant part of my manifest.json for Chrome:

{
  "manifest_version": 3,
  "name": "Katalog - Save any article to listen later",
  "version": "0.0.3",
  "permissions": ["activeTab", "tabs", "cookies"],
  "host_permissions": [
    "http://*/*",
    "https://*/*"
  ],
  "background": {
    "service_worker": "src/background.ts"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["src/content.ts"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

⚠️ An important note: I ran into issues when I tried to restrict host_permissions to just my web app’s domain. Chrome wouldn’t return the cookies I needed. Only after allowing access to all domains did it work. This feels like a bug in Chrome’s extension API. I couldn’t find any alternatives. However, as long as you describe the intent for using such broad host_permissions clearly when submitting the extension, I haven’t had any issues with publishing updates.

6. The login flow

When a user clicks the extension icon, the extension checks for the auth cookies. If they’re missing, it opens the login page in a new tab. I added a query parameter so the login page can show a message specific to extension users.

// background.ts
if (!hasAuth) {
  await browser.windows.create({
    url: import.meta.env.VITE_KATALOG_BASE_URL + "login?browser-extension=true",
    focused: true,
    type: "normal",
  });
}
Enter fullscreen mode Exit fullscreen mode

The login page of Katalog has an additional banner that indicates the user is trying to sign in from the Chrome extension

Right now, there’s no way for the extension to know immediately when the user finishes logging in. You have to click the extension icon again after logging in to save the current URL. I’d like to improve this in the future, maybe by using browser messaging or a custom event.

7. Content script and UX

When the extension is used on a page, it injects a content script that displays a floating element. This shows the progress as the article is being parsed and saved. I kept this UI minimal on purpose—no extra popups or login prompts.

The floating UI that's being injected by the extension to indicate progress as Katalog parses the article

8. Server-Side: CORS and Credentials

On the server side (web app), I had to make sure that credentials are allowed in CORS headers. Here’s a snippet from my Express server setup:

// server.js
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Credentials", "true");
  // ...other headers
  next();
});
Enter fullscreen mode Exit fullscreen mode

This is important so that the extension can make authenticated requests to the web app’s API endpoints.

9. Security and limitations

There are a few things to keep in mind with this approach:

  • The extension can only access cookies that aren’t HttpOnly. Supabase’s cookies are accessible in this way, but if you use a different auth provider, check their cookie settings.
  • By allowing access to all domains in host_permissions, you’re giving the extension broad access. This is something to be aware of, and you might want to review the permissions model if you’re building something more sensitive.
  • There’s no real-time notification to the extension when the user logs in. The user has to click the extension again after logging in.

10. What I’d improve next

  • I’d like to add a way for the extension to know when the user has finished logging in, maybe by using browser messaging or a custom callback.
  • I’d also like to tighten up the permissions if Chrome’s API allows it in the future.

11. Conclusion

This approach let me keep the extension simple and avoid duplicating authentication logic. I didn’t have to build a separate login UI or manage tokens in two places. If you’re already using Supabase (or any provider that uses cookies for auth), this is a lightweight way to add authentication to your extension.

Would you use cookie-based authentication for Chrome extensions? I'd love to hear your thoughts and feedback 🙌

Top comments (1)

Collapse
 
chengfeng_wang_ef3a072a75 profile image
chengfeng wang

When your extension sends a request to your API, how does the server know it’s coming from your extension? Also, do you send the request from the content script or the background script?