plasmo

Chrome Extension Authentication Boilerplate using PropelAuth

Learn how to easily authenticate your users in a Chrome extension using PropelAuth, a B2B authentication provider, and the Plasmo Framework.

Stefan Aleksic
Stefan AleksicMarch 2, 2023
cover image for blog

One of the trickiest parts of building a Chrome extension is authenticating your users. Our mission at Plasmo is to let developers focus on their product, not the intricacies of building browser extensions. Authentication is a pain point that a lot of browser extension devs have shared with us. That’s why we’ve worked with PropelAuth, a leading B2B authentication provider, to make the entire experience as easy and painless as possible.

This post will detail how to build a simple extension using PropelAuth for authentication.

1pnpm create plasmo "your-extension-name"

Now, go into the directory and run your dev server:

1cd your-extension name
2pnpm dev

Finally, follow the instructions to load the extension into Chrome and pin it so it’s easier to find:

By default, Plasmo sets you up with a file popup.tsx, let’s see what happens when we modify it:

1export default function IndexPopup() {
2    return <div>Hello!</div>
3}

Because it’s React-based, we also have all our usual hooks available. Let’s copy in React’s counter example:

1export default function IndexPopup() {
2    // Declare a new state variable, which we'll call "count"
3    const [count, setCount] = useState(0);
4
5    return (
6        <div style={{minWidth: "150px", textAlign: "center"}}>
7            <p>You clicked {count} times</p>
8            <button onClick={() => setCount(count + 1)}>
9                Click me
10            </button>
11        </div>
12    );
13}

This is just one type of page that you can create. Plasmo also allows you to create options pages, new tab pages, content scripts, and more. Check out the docs for a full list of features. Next, let’s see how we can add authentication to this extension.

Adding authentication to our Chrome Extension

We will use PropelAuth, an authentication provider designed specifically to get products live quickly.

As part of getting users live quickly, PropelAuth provides out of the box signup, login and profiles pages so we don’t have to worry about building them ourselves. If you follow the getting started guide, you can see how to customize those pages and add options for SSO (like ”Login with Google”).

Since the UIs are all managed for us, all we have to do is install @propelauth/react and then we can check if the user is logged in, get their information, redirect them to an account page, etc.

1yarn add @propelauth/react

Let’s update our popup.tsx to include signup/login buttons when the user isn’t logged in, and logout/account buttons when they are:

Since the UIs are all managed for us, all we have to do is install @propelauth/react and then we can check if the user is logged in, get their information, redirect them to an account page, etc.

1yarn add @propelauth/react

Let’s update our popup.tsx to include signup/login buttons when the user isn’t logged in, and logout/account buttons when they are:

1import React from "react"
2import {AuthProvider, useLogoutFunction, withAuthInfo, useHostedPageUrls} from "@propelauth/react";
3
4export default function IndexPopup() {
5    return (
6        <AuthProvider authUrl={process.env.PLASMO_PUBLIC_AUTH_URL}>
7            <Popup/>
8        </AuthProvider>
9    )
10}
11
12const Popup = withAuthInfo(function IndexPopup({isLoggedIn, user}) {
13    const logoutFn = useLogoutFunction()
14    const {getLoginPageUrl, getSignupPageUrl, getAccountPageUrl} = useHostedPageUrls()
15
16    // These pages are all hosted by PropelAuth on our domain
17    const openAccountPage = () => window.open(getAccountPageUrl())
18    const openSignupPage = () => window.open(getSignupPageUrl())
19    const openLoginPage = () => window.open(getLoginPageUrl())
20
21    if (isLoggedIn) {
22        return <div style={{minWidth: "250px", textAlign: "center"}}>
23            <p>You are logged in as <b>{user.email}</b></p>
24            <button onClick={openAccountPage}>Account</button>
25            <button onClick={() => logoutFn(false)}>Logout</button>
26        </div>
27    } else {
28        return <div style={{minWidth: "250px", textAlign: "center"}}>
29            <button onClick={openSignupPage}>Signup</button>
30            <button onClick={openLoginPage}>Login</button>
31        </div>
32    }
33});

The AuthProvider is responsible for fetching the user’s authentication status. withAuthInfo is responsible for injecting useful props like isLoggedIn and user. If you are building a B2B application, you’ll also find orgHelper and accessHelper useful.

And for a very basic integration, that’s it. Your users can log in, update their account information, send password reset emails, set up 2FA, etc.

However, you’ll probably want to send authenticated requests to your backend, so let’s see how we can do that with Plasmo’s Messaging API.

What’s hard about making requests from a Chrome extension?

Ideally, we want to just call fetch('/my-backend') or even fetch('https://api.mybackend.com/something') and have everything will just work. There are a few gotchas here, like CORS and CSPs.

CORS is a protocol that decides whether a cross-origin request (e.g. a request from https://somedomain.com to https://otherdomain.com) should be allowed. Chrome extensions are unique in that there are a few different places a fetch request can come from, and only some are subject to CORS.

To add to this, if you inject javascript into the user’s page and make fetches, you will also be subject to the page’s CSP (Content Security Policy), which could block the request.

How do we safely make requests from a Chrome Extension?

There are three places that fetches can come from:

  1. The extension itself (popup windows, options pages, etc.)
    1. This will work out of the box
  2. A background service worker.
    1. These are subject to CORS, and the requests will come from a URL like chrome-extension://somereallylongid
  3. Javascript that you inject into the user’s page. In Plasmo, this is called a Content Script.
    1. These are subject to CORS, and the requests will come from the page itself.
    2. These are also subject to the CSP.

The first two are pretty straightforward. However, the third one presents a unique challenge. Luckily, Plasmo provides a Messaging API that makes it easy to send messages between the parts of your extension. This allows you to write code like this:

1import { sendToBackground } from "@plasmohq/messaging"
2
3// Pass a message from a Content Script, over to a 
4//   background service worker
5async function sendInfoToBackgroundWorker(id: string) {
6    return await sendToBackground({
7        name: "ping",
8        body: { id }
9    })
10}

We can then write a handler background/messages/ping.ts to process it:

1import type { PlasmoMessaging } from "@plasmohq/messaging"
2 
3const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
4  const message = await querySomeApi(req.body.id)
5 
6  res.send({ message })
7}
8 
9export default handler

To make an authenticated request, we’ll need to get an access token for our user. Since these scripts are written in Javascript/Typescript, we cannot use @propelauth/react, but we can use @propelauth/javascript:

1yarn add @propelauth/javascript

And update our handler:

1const client = createClient({
2    authUrl: process.env.PLASMO_PUBLIC_AUTH_URL,
3    enableBackgroundTokenRefresh: true
4})
5
6const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
7    const authInfo = await client.getAuthenticationInfoOrNull();
8    const message = await querySomeApi(req.body.id, authInfo.accessToken)
9
10    res.send({
11        authInfo
12    })
13}

You’ll need to pass the access token in to the Authorization header like:

1function querySomeApi(id, accessToken) {
2  return fetch(`/item/${id}`, {
3    method: "GET",
4    headers: {
5      Authorization: `Bearer ${accessToken}`,
6    },
7  });
8}

Then, you can use one of PropelAuth’s backend libraries to verify the access token on the backend.

Summary

If you’ve ever tried to build an advanced Chrome Extension from scratch, you know how painful it is. Plasmo makes it possible to create powerful extensions with ease. By using the Plasmo Framework and adding PropelAuth for user authentication, you can streamline the development process and start iterating with your first customers today.

Back to Blog


Thanks for reading! We're Plasmo, a company on a mission to improve browser extension development for everyone. If you're a company looking to level up your browser extension, reach out, or sign up for Itero to get started.