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.
Top comments (0)