OAuth2 and AWS Cognito for Browser Extensions

RMAG news

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
chrome.runtime.onInstalled.addListener(async () => {
chrome.tabs.create({
url: chrome.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 = chrome.runtime.getManifest();

const AUTH_DOMAIN = <your-cognito-server-domain;
const AUTH_CLIENT_ID = manifest.oauth2.client_id;
const AUTH_REDIRECT_URL = chrome.getRedirectUrl(/);
const AUTH_RESPONSE_TYPE = code; // recommended
const AUTH_SCOPE = chrome.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 (!tokenResponse.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 chrome.storage.local.set(token, token);

Make sure to add the storage permission to your manifest.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 chrome.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 chrome.storage.get(token);
const token = storageGetResult[token] as Token | undefined;
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 chrome.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.

Leave a Reply

Your email address will not be published. Required fields are marked *