identity Permission — Chrome Extension Reference

9 min read

identity Permission — Chrome Extension Reference

Overview

manifest.json Setup

{
  "permissions": ["identity"],
  "oauth2": {
    "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
    "scopes": ["https://www.googleapis.com/auth/userinfo.email"]
  }
}

Key APIs

chrome.identity.getAuthToken()

chrome.identity.launchWebAuthFlow()

chrome.identity.removeCachedAuthToken()

chrome.identity.getProfileUserInfo()

Common Patterns

Google API Authentication

Third-Party OAuth

Token Refresh Pattern

Runtime Permission Check

import { checkPermission, requestPermission } from '@theluckystrike/webext-permissions';
const result = await checkPermission('identity');
if (!result.granted) {
  const req = await requestPermission('identity');
  if (!req.granted) return;
}

Security Considerations

Using with @theluckystrike/webext-messaging

Pattern: popup requests auth token from background, background manages token lifecycle:

type Messages = {
  getAuthToken: {
    request: { interactive: boolean };
    response: { token: string | null; error?: string };
  };
  signOut: {
    request: void;
    response: { success: boolean };
  };
  getUserProfile: {
    request: void;
    response: { email: string; id: string } | null;
  };
};

// background.ts
import { createMessenger } from "@theluckystrike/webext-messaging";
const msg = createMessenger<Messages>();

msg.onMessage({
  getAuthToken: async ({ interactive }) => {
    try {
      const token = await chrome.identity.getAuthToken({ interactive });
      return { token: token.token };
    } catch (err) {
      return { token: null, error: (err as Error).message };
    }
  },
  signOut: async () => {
    const token = await chrome.identity.getAuthToken({ interactive: false });
    if (token.token) {
      await chrome.identity.removeCachedAuthToken({ token: token.token });
      // Also revoke on the server
      await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${token.token}`);
    }
    return { success: true };
  },
  getUserProfile: async () => {
    const userInfo = await chrome.identity.getProfileUserInfo({ accountStatus: "ANY" as any });
    if (!userInfo.email) return null;
    return { email: userInfo.email, id: userInfo.id };
  },
});

Using with @theluckystrike/webext-storage

Store authentication state and user preferences:

import { defineSchema, createStorage } from "@theluckystrike/webext-storage";

const schema = defineSchema({
  isSignedIn: false,
  userEmail: "",
  lastAuthTime: 0,
  oauthProvider: "google" as "google" | "github" | "custom",
  refreshTokens: {} as Record<string, string>,
});
const storage = createStorage({ schema });

// Update auth state after sign-in
async function onSignIn(email: string) {
  await storage.setMany({
    isSignedIn: true,
    userEmail: email,
    lastAuthTime: Date.now(),
  });
}

// Watch for sign-out
storage.watch("isSignedIn", (signedIn) => {
  if (!signedIn) {
    console.log("User signed out — clearing cached data");
  }
});

Practical Example: GitHub OAuth via launchWebAuthFlow

const GITHUB_CLIENT_ID = "your_github_client_id";

async function signInWithGitHub(): Promise<string | null> {
  const redirectUrl = chrome.identity.getRedirectURL("github");
  const authUrl = new URL("https://github.com/login/oauth/authorize");
  authUrl.searchParams.set("client_id", GITHUB_CLIENT_ID);
  authUrl.searchParams.set("redirect_uri", redirectUrl);
  authUrl.searchParams.set("scope", "read:user user:email");

  try {
    const responseUrl = await chrome.identity.launchWebAuthFlow({
      url: authUrl.toString(),
      interactive: true,
    });

    const url = new URL(responseUrl);
    const code = url.searchParams.get("code");
    if (!code) return null;

    // Exchange code for token via your backend
    const resp = await fetch("https://your-backend.com/auth/github/token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ code }),
    });
    const { access_token } = await resp.json();
    return access_token;
  } catch (err) {
    console.error("GitHub auth failed:", err);
    return null;
  }
}

Practical Example: Token Refresh with Retry

async function fetchWithAuth(url: string, options: RequestInit = {}): Promise<Response> {
  let token = (await chrome.identity.getAuthToken({ interactive: false })).token;

  let response = await fetch(url, {
    ...options,
    headers: { ...options.headers, Authorization: `Bearer ${token}` },
  });

  if (response.status === 401 && token) {
    // Token expired — remove and get fresh one
    await chrome.identity.removeCachedAuthToken({ token });
    token = (await chrome.identity.getAuthToken({ interactive: false })).token;

    response = await fetch(url, {
      ...options,
      headers: { ...options.headers, Authorization: `Bearer ${token}` },
    });
  }

  return response;
}

Gotchas

Common Errors

Frequently Asked Questions

How do I implement OAuth in Chrome extensions?

Use the chrome.identity API to launch OAuth flows. You’ll need to configure redirect URIs and handle the token exchange.

Is chrome.identity free?

Yes, the chrome.identity API is free to use, but you’ll need to set up OAuth with your identity provider (Google, Auth0, etc.). —

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article