How To Implement OAuth in a Chrome Extension

These are general guidelines on how to implement OAuth in a Chrome extension.

Things to note before getting started:

  • The permissions of the token you will be sending from your server must be safe enough to be exposed publicly. 
  • This method only verified to work with Chrome, Firefox, and Safari Desktop extensions. Safari on iOS has special security precautions when redirecting to a non HTTPS protocol, so this method will need some modifications to work on Safari for iOS. Ideally, you would use the native iOS app to handle logins for your extension.
  • To fully understand this tutorial, some knowledge of how Chrome extensions work is required. Please check the documentation for basic information on getting a Chrome extension set up and how they work.
  • The v2 manifest version is used in the examples.
  • Here is the example code used in this tutorial: Example.zip

Get Started

Here is a basic layout for a Chrome extension, yours will probably differ:

The first step in this process is creating an HTML page that your server will redirect to. This HTML page will be hosted within the extension itself, not on a server, which helps maintain a secure environment.

The common pattern that we see is having the HTML page display a loading screen while your .js file parses the URL to grab the token passed back from your server.

Here are the example pages we have created:

oauth.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading</title>
</head>
<body>
<div>Loading...</div>
<script src="oauth.js"></script>
</body>
</html>

oauth.js

// Example URL: <https://examplelogin.com?access_token=token&=refresh_token=token>
const url = new URL(window.location.href);
const accessToken = url.searchParams.get("access_token");
const refreshToken = url.searchParams.get("refresh_token");

chrome.runtime.sendMessage({
type: "OAUTH_TOKEN_RECIEVED",
payload: {
accessToken,
refreshToken,
},
});

Update your manifest.json and specify your oauth.html file, so it can be accessed as a resource. You will also need the “tabs” permission for the later steps.

{
"manifest_version": 2,
"name": "Example",
"description": "Example",
"version": "1.0.0",

"background": {
"scripts": ["background.js"]
},

"content_scripts": [
{
"js": ["content.js"],
"matches": ["<all_urls>"]
}
],
"web_accessible_resources": ["oauth.html"],
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/toolbar-icon-16.png",
"19": "images/toolbar-icon-19.png",
"32": "images/toolbar-icon-32.png",
"38": "images/toolbar-icon-38.png"
}
},

"permissions": ["tabs"]
}

Request the Token

Now that we have built out the pages to receive the token, we can work on requesting the token.

Let's add a login button to our popup.html file, so we can use the background script to navigate to our hosted login page.

Here is our popup.html page:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="popup.css" />
</head>
<body>
<button>Login</button>
<script src="popup.js"></script>
</body>
</html>

Here is our popup.js file:

const button = document.querySelector("button");

button.onclick = () =>
chrome.runtime.sendMessage({
type: "OAUTH_TOKEN_REQUEST",
});

This will create a simple HTML page that displays when you click the icon in your browser:

Now, when we click the login button, it sends a message to that background script to request the OAuth token. We must modify our background.js file to handle the request:

chrome.runtime.onMessage.addListener(({ type, payload }) => {
switch (type) {
case "OAUTH_TOKEN_REQUEST":
const oauth = chrome.runtime.getURL("oauth.html");
const exampleLoginPage = "<https://examplelogin.com>"; // <- Replace this with your login URL
const url = new URL(exampleLoginPage);
url.searchParams.append("ext", oauth);
console.log(url.toString());
break;
}
});

If you were to check your background console, you should see an output like this:

<https://examplelogin.com/?ext=chrome-extension%3A%2F%2Fjiaieifpmjjbmaghajbhhppmhfgbikpp%2Foauth.html>

Note:The example link will be replaced by your login URL.

We can continue by calling chrome.tabs.create to open the login page while passing the auth page as the query parameter: 

chrome.runtime.onMessage.addListener(({ type, payload }) => {
switch (type) {
case "OAUTH_TOKEN_REQUEST":
const oauth = chrome.runtime.getURL("oauth.html");
const exampleLoginPage = "<https://examplelogin.com>"; // <- Replace this with your login URL
const url = new URL(exampleLoginPage);
url.searchParams.append("ext", oauth);
chrome.tabs.create({
active: true,
url: url.toString(),
});
break;
}
});

When the user clicks login, it will open your login page.

After the user has logged in, the parameter will be forwarded to your backend. It's important to do some validation with the ext parameter. For example:

if (!req.query.ext.startsWith('chrome-extension')) {
res.status(400).send()
}

Option #1: Send access token

After the user has logged in successfully, redirect the user to the URL located in the ext parameter with the token attached.

const url = req.query.ext + `?access_token=${accessToken}&refresh_token=${refreshToken}`
res.redirect(url)

Store the Token

After the redirect is successful, your oauth.js file will be able to send the token back to your background script to store.

chrome.runtime.onMessage.addListener(({ type, payload }) => {
switch (type) {
case "OAUTH_TOKEN_REQUEST":
const oauth = chrome.runtime.getURL("oauth.html");
const exampleLoginPage = "<https://examplelogin.com>"; // <- Replace this with your login URL
const url = new URL(exampleLoginPage);
url.searchParams.append("ext", oauth);
chrome.tabs.create({
active: true,
url: url.toString(),
});
break;
case "OAUTH_TOKEN_RECIEVED":
const { accessToken, refreshToken } = payload;
console.log("Recieved: ", accessToken, refreshToken);
break;
}
});

Option #2: Send tracking code

Alternatively, you can send the tracking code you would like used for the user directly, avoiding the OAuth flow. The UUID is the only value required by the extension so things can be simplified by just providing the extension with the UUID that should be used as the tracking code.

const url = req.query.ext + `?uuid=<TRACKING CODE>`
res.redirect(url)

 

You can test this without logging in by grabbing your URL from the background console by calling:

chrome.runtime.getURL("oauth.html")

Or by adding example tokens in the URL and pasting the link in your browser:

 

Congratulations, you have implemented OAuth in a Chrome Extension!