Choosing Between getAuthToken and launchWebAuthFlow

chrome.identity.getAuthToken is shorter and Chrome-only; launchWebAuthFlow is portable and more work. Compare the two on scope, caching, sign-out and store review.

Published August 1, 2026 Updated August 1, 2026 8 min read
Table of Contents

chrome.identity.getAuthToken is four lines and returns a working Google access token. chrome.identity.launchWebAuthFlow is about eighty lines and works everywhere. Picking the short one because it is short is how extensions end up rewriting their entire authentication layer six months later when the Firefox port starts. This page is part of Identity & OAuth Authentication and lays out what each method actually gives you so the decision is made once, deliberately.

Root cause: they solve different problems that look the same

getAuthToken is not a general OAuth client. It is a bridge to the Google account already signed into the Chrome profile: the browser owns the credential, the browser caches the token, and the browser decides when to show a consent screen. launchWebAuthFlow is a generic redirect catcher — it opens a URL and tells you where it ended up. Everything else, including the token, is yours to manage.

How much of the flow each method ownsgetAuthToken hands the browser responsibility for the account, consent, token and cache; launchWebAuthFlow only owns the window and the redirect capture.Account selectionwhich identity signs ingetAuthToken: profile account onlyConsent UIscopes shown to the userboth: browser-renderedToken acquisitioncode exchange, PKCElaunchWebAuthFlow: your codeToken cache and refreshwho renews itgetAuthToken: browser-managedRevocationclearing the grantboth: your call to the provider
The lines your code does not write are lines the browser wrote for exactly one provider.

Step 1. Answer the portability question first

If the extension will ever ship on Firefox or Safari, the decision is already made — getAuthToken does not exist there, and there is no polyfill, because there is no equivalent browser-managed account to bridge to. Everything after this is only relevant to Chrome-and-Edge-only products.

The decision, in the order the questions actually matterPortability rules out getAuthToken outright; a non-Google provider rules it out again; only a Chrome-only Google integration reaches the shortcut.Will this ship anywhere but Chrome and Edge?YeslaunchWebAuthFlowthe only portable optionWrite the eighty lines onceone path everywhereNo, Chrome onlyIs the provider Google?getAuthToken is Google-boundNo → still launchWebAuthFlowthere is no other bridgeChrome + GooglegetAuthToken is viabletwenty lines, browser-managedAccept: profile account, cache-only s…and manifest-fixed scopes
Two questions eliminate the shortcut for most products before any code is written.
1// capability probe — run it once and log the result during evaluation
2const hasNativeGoogleAuth = typeof chrome !== "undefined"
3  && typeof chrome.identity?.getAuthToken === "function";
4
5console.log("getAuthToken available:", hasNativeGoogleAuth);

Execution context: Service worker or any extension page. In Chrome and Edge this logs true; in Firefox and Safari chrome.identity exists — mapped from browser.identity — but getAuthToken is absent, so the probe reads false without throwing. Feature-probing the method rather than the browser is the pattern described in feature detection instead of browser sniffing, and it is what lets a single build fall back cleanly.

Step 2. Compare the code you actually maintain

The Chrome path relies on the oauth2 manifest key, which the browser reads to decide which client and scopes to request.

1{
2  "permissions": ["identity"],
3  "oauth2": {
4    "client_id": "1234567890-abcdef.apps.googleusercontent.com",
5    "scopes": ["https://www.googleapis.com/auth/drive.file"]
6  },
7  "key": "MIIBIjANBgkq…"   // pins the extension ID so the client_id keeps matching
8}

Execution context: Manifest, read at install time. The oauth2 key is ignored entirely by Firefox and Safari — it is not an error there, it simply does nothing, which is why a broken Chrome-only auth path can survive a cross-browser build unnoticed. The key field pins the extension ID during development so the OAuth client registration keeps matching before the extension is published.

1// sw.js — the entire Chrome-only sign-in
2async function getGoogleToken({ interactive = false } = {}) {
3  const { token } = await chrome.identity.getAuthToken({ interactive });
4  return token;
5}
6
7async function invalidate(token) {
8  await chrome.identity.removeCachedAuthToken({ token });   // next call fetches a fresh one
9}

Execution context: Service worker. There is no code exchange, no PKCE, no expiry tracking and no refresh token — the browser holds all of it. The catch is removeCachedAuthToken: the browser’s cache does not notice a token the resource server has rejected, so a 401 from a Google API must be followed by an explicit invalidation and one retry, or the same dead token is handed back on every call.

Step 3. Weigh the four differences that bite later

The trade-offs that surface after launchPortability, account choice, sign-out behaviour and store review compared between the two identity methods.ConcerngetAuthTokenlaunchWebAuthFlowRuns on Firefox and SafariNoYesNon-Google providersNoAny OAuth 2 serverUser picks the accountProfile accountProvider decidesSign-out inside the extensionCache clear onlyFull revocationToken storage codeNone neededYou write itReview scrutiny of scopesDeclared in manifestRequested at runtime
The two rows that surprise teams are account choice and sign-out — neither is under your control with getAuthToken.

Account choice is the one that generates support tickets. getAuthToken uses the account signed into the Chrome profile; a user with a personal profile and a work Google account cannot choose the other one from inside your extension. launchWebAuthFlow sends them to Google’s own account chooser, where they can.

Sign-out is the second. removeCachedAuthToken clears the browser’s copy and nothing else — the grant still exists on the user’s Google account, so the next call signs them straight back in with no prompt. To genuinely sign out you must call the revocation endpoint yourself, at which point you are writing part of the flow you chose getAuthToken to avoid.

The third is scope escalation. With getAuthToken the scopes are baked into the manifest, so adding one means shipping a new version and waiting for every user to receive it before the feature can work — and the consent screen appears at a moment the user did not choose, usually the first time the extension happens to wake up after the update. launchWebAuthFlow builds its scope list at call time, so an incremental grant can be requested exactly when the user turns the feature on, with your own explanation on screen next to it. For an extension that starts with read access and later offers write, that difference is the whole onboarding story.

The fourth is testability. The native path cannot be exercised without a real signed-in Chrome profile, which makes it awkward in continuous integration: the browser owns the account, so there is nothing to stub at the API boundary. The portable path is an ordinary fetch against a token endpoint, which a local fixture server can stand in for, so the whole flow runs headless. If your test suite is expected to cover sign-in rather than skip it, that is worth weighing before choosing.

Step 4. Ship both when it is worth it

For a Chrome-first product with a real cross-browser roadmap, the pragmatic answer is one interface with two implementations selected by capability. Keep the seam narrow: everything above it asks for a token and knows nothing else.

1// auth/index.js — the only module that knows which path is in use
2import * as native from "./native-google.js";
3import * as web from "./web-auth-flow.js";
4
5const impl = typeof chrome.identity?.getAuthToken === "function" ? native : web;
6
7export const getAccessToken = impl.getAccessToken;
8export const signIn = impl.signIn;
9export const signOut = impl.signOut;

Execution context: Service worker module, resolved once at script evaluation. Both modules must present the same three functions and the same rejection shapes, or the calling code grows engine-specific branches and you have lost the benefit. The dual path costs roughly the eighty lines of the portable implementation plus twenty for the native one — worth it when Chrome is the overwhelming majority of installs and the Google account integration is a headline feature, and not worth it otherwise.

Cross-browser variation

  • Chrome/Edge: both methods available. getAuthToken requires the oauth2 manifest key and works only with Google accounts; Edge implements it against Microsoft’s Chromium base and it behaves the same for Google clients.
  • Firefox: getAuthToken and removeCachedAuthToken are absent. launchWebAuthFlow and getRedirectURL are present with native Promises.
  • Safari: same as Firefox — no getAuthToken. launchWebAuthFlow is supported from Safari 16.4 with an app-derived redirect base.
  • All engines: the oauth2 manifest key is silently ignored where it is not implemented, so a missing token on a non-Chrome build looks like a runtime bug rather than a configuration gap.

Verification

  1. Load the extension in Chrome and confirm the capability probe logs true, then in Firefox and confirm it logs false without throwing.
  2. With the native path active, call a Google API with a manually corrupted token. The first call should 401, removeCachedAuthToken should run, and the retry should succeed.
  3. Sign out through your UI, then call getAuthToken({ interactive: false }). If it still returns a token, the grant was never revoked — only the cache was cleared.
  4. Switch the Chrome profile to a second Google account and reopen the extension. With getAuthToken the identity follows the profile; with the portable path the account chooser appears.

FAQ

Can I use getAuthToken for a non-Google provider?

No. It is wired to the browser’s Google account integration and has no provider parameter.

Does getAuthToken avoid the install-time permission warning?

No. Both paths need the identity permission and produce the same warning. The oauth2 scopes are additionally shown by Google’s own consent screen.

Is launchWebAuthFlow slower for the user?

Marginally, on the first sign-in, because the provider renders a page. On subsequent silent refreshes there is no UI at all in either path.

Which one do reviewers prefer?

Neither is favoured. What matters is that the scopes you request match what the extension does — the argument you make in writing a permission justification that passes.

Other Core APIs & Cross-Browser Data Management Resources