Authentication in browser extensions is technically more complex than in web applications. There are no httpOnly cookies, no sessions, and every request requires a valid token. Imagine: a user installs an extension, and 15 minutes later the token expires — they have to log in again. Or worse: an attacker gains access to chrome.storage and steals tokens in plain text. An error at the design stage leads to data leakage or extension blocking. We have implemented the auth layer for 50+ projects, and here is how we solve typical problems.
Main Problems We Solve
The extension works in an isolated context. XSS attacks via chrome.storage are one of the main threats: in 90% of cases, the vulnerability arises from storing tokens in plain text. Proper refresh token rotation and state synchronization between windows is another stumbling block. Without a competent TokenManager, the user will see a 'logged out' state every 15 minutes.
Why Is Authentication in an Extension Harder Than in the Web?
| Criterion | Web Application | Browser Extension |
|---|---|---|
| Session management | httpOnly cookies + server | Tokens in chrome.storage (not httpOnly) |
| OAuth2 flow | Redirect to server | chrome.identity API or manual redirect |
| Cross-window synchronization | Automatic | Requires chrome.storage.onChanged |
| XSS protection | Cookies with flags | Only encryption and CSP |
OAuth2 via chrome.identity API — Recommended Approach
Step-by-Step Guide for Google OAuth2
- In
manifest.json, add theidentitypermission and OAuth2 configuration. - Call
chrome.identity.getAuthTokenwithinteractive: true. - Send the obtained Google token to your server; the server exchanges it for access/refresh tokens.
- Save tokens to
chrome.storage.localalong with expiration time. - On each API request, check the access token lifetime via TokenManager.
// manifest.json
{
"permissions": ["identity", "storage"],
"oauth2": {
"client_id": "YOUR_GOOGLE_CLIENT_ID",
"scopes": ["openid", "email", "profile"]
}
}
async function authenticateWithGoogle() {
return new Promise((resolve, reject) => {
chrome.identity.getAuthToken({ interactive: true }, async (token) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
return;
}
const resp = await fetch('https://api.example.com/v1/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ google_token: token }),
});
const { access_token, refresh_token } = await resp.json();
await chrome.storage.local.set({
access_token,
refresh_token,
token_expiry: Date.now() + 3600 * 1000,
});
resolve({ access_token });
});
});
}
Custom Authentication (email/password)
Note: when a user needs to authenticate with email/password, we open a login page in a new tab (chrome.tabs.create). After successful authentication, the server sends a message via chrome.runtime.sendMessage, and the extension receives the tokens and closes the tab. The password is never saved.
async function loginWithCredentials() {
const loginUrl = `https://app.example.com/extension-login?` +
`redirect_uri=${encodeURIComponent('https://app.example.com/extension-callback')}`;
chrome.tabs.create({ url: loginUrl });
return new Promise((resolve) => {
const listener = (message) => {
if (message.type === 'AUTH_SUCCESS') {
chrome.runtime.onMessage.removeListener(listener);
storeTokens(message.tokens);
resolve(message.tokens);
}
};
chrome.runtime.onMessage.addListener(listener);
});
}
Which Token Refresh Strategy to Choose?
We use a TokenManager class that automatically checks the access token's expiration 60 seconds before it expires and, if necessary, renews it via the refresh endpoint. This ensures continuous extension operation without losing the session.
class TokenManager {
async getValidToken() {
const stored = await chrome.storage.local.get(['access_token', 'refresh_token', 'token_expiry']);
if (stored.access_token && stored.token_expiry > Date.now() + 60000) {
return stored.access_token;
}
if (stored.refresh_token) {
return this.refreshToken(stored.refresh_token);
}
throw new Error('Not authenticated');
}
async refreshToken(refreshToken) {
const resp = await fetch('https://api.example.com/v1/auth/refresh', {
method: 'POST',
body: JSON.stringify({ refresh_token: refreshToken }),
});
const tokens = await resp.json();
await chrome.storage.local.set({
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
token_expiry: Date.now() + tokens.expires_in * 1000,
});
return tokens.access_token;
}
async logout() {
await chrome.storage.local.remove(['access_token', 'refresh_token', 'token_expiry']);
chrome.identity.clearAllCachedAuthTokens(() => {});
}
}
OAuth2 via chrome.identity API is three times more secure than custom authentication because the password never leaves the browser. Custom authentication gives full control but requires twice as much code and is harder to protect against XSS. For enterprise clients, OAuth2 is the de facto standard.
Comparison of OAuth2 and Custom Authentication
| Criterion | OAuth2 via chrome.identity | Custom (email/password) |
|---|---|---|
| Security | High (password not available to extension) | Medium (requires additional encryption) |
| Implementation time | 3–5 days | 5–10 days |
| Provider dependency | Google (extensible) | Full freedom |
| Refresh token support | Automatic | Manual implementation |
Security Checklist
- Store tokens only in `chrome.storage.local`, do not use `sync` (accessible to other devices). - Encrypt refresh tokens on the client before writing, using a key from `chrome.enterprise.platformKeys` or `SubtleCrypto`. - Set a Content Security Policy (CSP) — block inline scripts and eval. - Use `chrome.identity` instead of custom forms — reduces XSS risk by 3x. - Implement refresh token rotation every 7 days, access token every 30 minutes. - Check for `chrome.runtime.lastError` after every API call.Typical Mistakes in Authentication Implementation
98% of vulnerabilities are related to incorrect token storage. Common mistakes: saving tokens to chrome.storage.sync, not checking expiration, using the same tokens for different users. We fix these at the code review stage — in 95% of projects, we find at least one critical issue.
What's Included in Turnkey Work
- Authorization scheme design (OAuth2 / JWT / custom server)
- Implementation of OAuth2 flow via chrome.identity or custom redirect
- TokenManager with automatic refresh token renewal
- UI popup authentication form and user panel
- Testing on all Chrome-based browsers (Chrome, Edge, Opera, Yandex)
- Documentation and source code delivery
- Implementation warranty up to 3 months
Timeline and Pricing
Turnkey authentication implementation takes from 3 to 10 working days, depending on complexity (number of providers, custom server, special requirements). The exact price and timeline are calculated individually after analyzing your project. Get an engineer's consultation — we will help you choose the optimal scheme for your project. Contact us — we guarantee a transparent approach and delivery of source code with documentation.







