OAuth2 and AWS Cognito for Browser Extensions
Lorenzo Rivosecchi
Posted on April 15, 2024
This is a guick guide on how to do OAuth2 logins within a chrome extension. Let's get started:
Step 1: Register the Extension
OAuth2 requires a static URL to redirect the client after the authentication with the third party server is completed.
Since Browser Extensions are bound to a traditional URL, browsers rely on a trick.
Extensions can be registered to the Chrome Webstore and obtain a subdomain on chromiumapps.org
:
<extension-id>.chromiumapps.org
After an OAuth2 flow is initiated, the browser will listen for redirects on that URL and expose the authentication code through a special API, more on this later.
To register the extension follow this guide from Google.
Before proceeding, make sure that your manifest.json
contains the following fields:
{
"key": "-----BEGIN PUBLIC KEY-----...",
"permissions": ["identity"]
}
Step 2: Setup AWS Cognito
Assuming that you already have a Cognito User Pool,
the next step is to create a client application.
Set the redirect URL to the following:
https://<extension-id>.chromiumapps.org/
Take note of the Client ID issued by AWS Cognito and put it in the manifest.json
of your extension.
{
"oauth2": {
"client_id": "<your-client-id>",
"scopes": ["email", "openid", "profile"]
}
}
Step 3: Write the Code
Here is how the auth flow will look like:
- User installs the App
- Browser opens a new tab with an auth page
- User clicks on Login
- Browser opens a popup window with cognito hosted ui
- User logs in from the popup
- Browser closes popup and show success message
Open new tab on install
Add the following code to your background script:
// background.js
browser.runtime.onInstalled.addListener(async () => {
browser.tabs.create({
url: browser.runtime.getURL("auth.html"),
});
});
Create login button
Add an auth.html
page to your extension folder with a login button and a script tag pointing to auth.js
<head>
<title>Auth</title>
</head>
<body>
<button id="login_button">Login</button>
<script src="/auth.js"></script>
</body>
Connect the button to a login
function that we are going to complete later.
async function login() {
console.log("Logging in...");
}
const loginButton = document.getElementById("login_button");
loginButton.addEventListener("click", () => {
login().catch(console.error);
});
Initiate OAuth2 flow
Let's complete the login function step by step.
First, let's define some variables to use as parameters for
the auth server:
const manifest = browser.runtime.getManifest();
const AUTH_DOMAIN = "<your-cognito-server-domain";
const AUTH_CLIENT_ID = manifest.oauth2.client_id;
const AUTH_REDIRECT_URL = browser.getRedirectUrl("/");
const AUTH_RESPONSE_TYPE = "code"; // recommended
const AUTH_SCOPE = browser.oauth2.scopes.join(" ");
Then we use them to build an authorization
request:
// https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
const authorizeUrl = new URL("oauth2/authorize", `https://${AUTH_DOMAIN}`);
authorizeUrl.searchParams.set("client_id", AUTH_CLIENT_ID);
authorizeUrl.searchParams.set("redirect_uri", AUTH_REDIRECT_URL);
authorizeUrl.searchParams.set("response_type", AUTH_RESPONSE_TYPE);
authorizeUrl.searchParams.set("scope", AUTH_SCOPE);
Now it's time to call a api that will open the popup for us and tell the browser to listen for a redirect to <extension-id>.chromiumapps.org
.
const redirectUrl = await browser.identity.launchWebAuthFlow({
url: authorizeUrl.toString(),
interactive: true,
});
If your have set response_type
to code
, the redirectUrl
variable should look like this:
https://<your-extension-id>.chromiumapp.org/?code=1234
We can easily get the code using the URL
class:
const authCodeUrl = new URL(redirectUrl);
const authCode = authCodeUrl.searchParams.get("code");
Obtain a session token
Let's now exchange the code with a token using another endpoint from AWS Cognito:
const tokenUrl = new URL("oauth2/token", `https://${AUTH_DOMAIN}`);
const tokenRes = await fetch(tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
code: authCode,
grant_type: "authorization_code",
redirect_uri: AUTH_REDIRECT_URL,
client_id: AUTH_CLIENT_ID,
}),
});
if (!tokenRes.ok) {
throw new Error("Failed to fetch token");
}
const token = await tokenRes.json();
This token can now be stored for future use.
Since we are inside a Browser Extension, the recommended approach is to use the browser.storage
api instead of localStorage
.
await browser.storage.local.set("token", token);
Make sure to add the
storage
permission to yourmanifest.json
Get user info
Now that we have a session token we can call the userInfo
endpoint to retrieve the users email, username and other data depending on the setup.
// auth.js
async function fetchUser() {
// Retrieve token from storage
const storageGetResult = await browser.storage.get("token");
const token = storageGetResult["token"] as Token | undefined;
if (!token) throw new Error("Not logged in.");
// Fetch user info using the access token
const userInfoUrl = new URL("oauth2/userInfo", `https://${AUTH_DOMAIN}`);
const userInfoRes = await fetch(userInfoUrl, {
headers: {
Authorization: `Bearer ${token.access_token}`,
},
});
const user = await userInfoRes.json();
return user;
}
Logout
Finally, let's write a function to log out the user by revoking the token and removing it from the local store:
// auth.js
async function logout() {
// Retrieve token from storage
const storageGetResult = await browser.storage.get("token");
const token = storageGetResult["token"];
if (!token) throw new Error("Not logged in.");
// Revoke token on the server using the refresh token
const url = new URL("oauth2/revoke", `https://${AUTH_DOMAIN}`);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
token: token.refresh_token,
client_id: AUTH_CLIENT_ID,
}),
});
if (!response.ok) throw new Error("Failed to revoke token");
// Remove the token from storage
await browser.storage.local.remove("token");
}
Conslusions
And that's it. You should have all you need to develop a Browser Extension with OAuth2 and AWS Cognito.
Posted on April 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.