OAuth 2.0 Authentication in Chrome Extensions: Complete Guide (2026)

AppBooster Team · · 4 min read
Security lock representing OAuth authentication

OAuth 2.0 for Chrome Extensions

OAuth 2.0 lets your extension access Google APIs (Gmail, Drive, Calendar, Contacts) on behalf of users — without handling passwords. Chrome’s Identity API (chrome.identity) simplifies the entire OAuth flow, managing tokens and consent screens automatically.


Architecture: Why Extensions Need Special Handling

Extensions can’t use the standard OAuth redirect flow because they don’t load over HTTPS. Chrome’s Identity API provides:

  • Built-in consent screen management
  • Automatic token caching and refresh
  • No redirect URI configuration needed
  • Silent authentication for returning users

Step 1: Get a Stable Extension ID

Your OAuth credentials are tied to your extension ID, so it must stay consistent:

  1. Upload your extension to the Chrome Developer Dashboard (no need to publish)
  2. Go to Package tab → Copy the public key
  3. Add it to manifest.json (single line, no newlines):
{
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
}
  1. Verify your ID matches on chrome://extensions

Step 2: Create OAuth Credentials

  1. Go to Google API Console
  2. Create or select a project
  3. Navigate to CredentialsCreate CredentialsOAuth 2.0 Client ID
  4. Select Chrome Extension as application type
  5. Enter your extension ID
  6. Enable the APIs you need (e.g., People API, Gmail API, Drive API)
  7. Create an API key for the project

Step 3: Configure the Manifest

{
  "manifest_version": 3,
  "name": "My Google Integration",
  "version": "1.0",
  "permissions": ["identity", "storage"],
  "oauth2": {
    "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
    "scopes": [
      "https://www.googleapis.com/auth/contacts.readonly",
      "https://www.googleapis.com/auth/userinfo.profile"
    ]
  },
  "action": {
    "default_popup": "popup.html"
  }
}

Common OAuth Scopes

ScopeAccess
userinfo.profileBasic profile info
userinfo.emailEmail address
contacts.readonlyRead contacts
gmail.readonlyRead Gmail messages
drive.readonlyRead Google Drive files
calendar.readonlyRead calendar events
spreadsheets.readonlyRead Google Sheets

Step 4: Implement Authentication

Get an OAuth Token

async function getAuthToken(interactive = true) {
  return new Promise((resolve, reject) => {
    chrome.identity.getAuthToken({ interactive }, (token) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
        return;
      }
      resolve(token);
    });
  });
}

Make Authenticated API Calls

async function fetchUserProfile() {
  const token = await getAuthToken();

  const response = await fetch(
    'https://www.googleapis.com/oauth2/v2/userinfo',
    {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );

  if (response.status === 401) {
    // Token expired — remove and retry
    await removeCachedToken(token);
    return fetchUserProfile();
  }

  return response.json();
}

async function fetchContacts() {
  const token = await getAuthToken();
  const apiKey = 'YOUR_API_KEY';

  const response = await fetch(
    `https://people.googleapis.com/v1/people/me/connections?personFields=names,emailAddresses&key=${apiKey}`,
    {
      headers: { Authorization: `Bearer ${token}` }
    }
  );

  return response.json();
}

Token Management

async function removeCachedToken(token) {
  return new Promise((resolve) => {
    chrome.identity.removeCachedAuthToken({ token }, resolve);
  });
}

async function signOut() {
  const token = await getAuthToken(false);
  if (token) {
    // Revoke the token server-side
    await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${token}`);
    await removeCachedToken(token);
  }
}

Complete Popup Example

// popup.js
document.getElementById('sign-in-btn').addEventListener('click', async () => {
  try {
    const token = await getAuthToken(true);
    const profile = await fetchUserProfile();

    document.getElementById('user-name').textContent = profile.name;
    document.getElementById('user-email').textContent = profile.email;
    document.getElementById('user-avatar').src = profile.picture;

    document.getElementById('signed-out').style.display = 'none';
    document.getElementById('signed-in').style.display = 'block';
  } catch (error) {
    document.getElementById('error').textContent = error.message;
  }
});

document.getElementById('sign-out-btn').addEventListener('click', async () => {
  await signOut();
  document.getElementById('signed-out').style.display = 'block';
  document.getElementById('signed-in').style.display = 'none';
});

// Auto-sign-in on popup open (non-interactive)
(async () => {
  try {
    const token = await getAuthToken(false);
    if (token) {
      const profile = await fetchUserProfile();
      // Update UI...
    }
  } catch {
    // Not signed in — show sign-in button
  }
})();

Common Pitfalls

  1. Mismatched extension IDs — Your unpacked ID must match the Dashboard ID. Always use the key field.
  2. Forgetting to enable APIs — Creating OAuth credentials isn’t enough. Enable each API in the Google Cloud Console.
  3. Scope changes after publish — Adding new scopes requires users to re-authorize. Plan your scopes upfront.
  4. Not handling token expiry — Tokens expire. Always handle 401 responses by clearing the cache and retrying.
  5. Interactive flag in service workersinteractive: true requires a user gesture context. Call from popup or options page.

What’s Next

OAuth 2.0 via Chrome’s Identity API gives your extension secure access to the entire Google ecosystem. Start with read-only scopes, handle token lifecycle properly, and always provide a clear sign-out option.

Grow your authenticated extension with ExtensionBooster’s developer tools.

Share this article

Build better extensions with free tools

Icon generator, MV3 converter, review exporter, and more — no signup needed.

Related Articles