This is a short, code‑first tutorial for developers. It assumes you already have a Cloud Run service and an OAuth2 Web Client. No business case study here—just the practical bits.
Summary
- Use
chrome.identity.launchWebAuthFlow
to get a Google ID token. - Send it as
Authorization: Bearer <token>
to your IAP/IAM‑protected Cloud Run endpoint. - Verify the token audience on the server and cache tokens with a small expiry buffer on the client.
1) Getting an ID token (MV3)
background.js
async function getIdToken() {
const redirectUrl = chrome.identity.getRedirectURL();
const u = new URL("https://accounts.google.com/o/oauth2/v2/auth");
u.searchParams.set("client_id", "YOUR_OAUTH_CLIENT_ID.apps.googleusercontent.com");
u.searchParams.set("response_type", "id_token");
u.searchParams.set("scope", "openid email profile");
u.searchParams.set("redirect_uri", redirectUrl);
u.searchParams.set("nonce", String(Date.now()));
u.searchParams.set("prompt", "select_account");
const { url } = await chrome.identity.launchWebAuthFlow({ url: u.toString(), interactive: true });
const params = new URLSearchParams(new URL(url).hash.substring(1));
const idToken = params.get("id_token");
if (!idToken) throw new Error("No id_token returned");
return idToken;
}
Mini‑cache (optional):
function decodePayload(jwt){const b=jwt.split('.')[1].replace(/-/g,'+').replace(/_/g,'/');return JSON.parse(atob(b));}
async function getCachedIdToken(){
const now=Math.floor(Date.now()/1000);
const {token,exp}=await chrome.storage.local.get(["token","exp"]);
if(token && exp && (exp-300)>now) return token; // 5‑min buffer
const fresh=await getIdToken(); const {exp:e}=decodePayload(fresh);
await chrome.storage.local.set({token:fresh, exp:e}); return fresh;
}
2) Calling your API
popup.js
document.getElementById("go").addEventListener("click", async () => {
const token = await chrome.runtime.sendMessage({ type: "GET_TOKEN" });
const res = await fetch("https://YOUR‑SERVICE‑HASH‑ue.a.run.app/run-secure-endpoint", {
method: "POST",
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({ url: (await chrome.tabs.query({active:true,currentWindow:true}))[0].url })
});
console.log(await res.text());
});
And in background.js
:
chrome.runtime.onMessage.addListener((m,_,send)=>{
if(m?.type==="GET_TOKEN") getCachedIdToken().then(t=>send(t)).catch(e=>send({error:String(e)}));
return true;
});
3) Verifying on Flask (Cloud Run)
main.py
from flask import Flask, request, jsonify
from google.oauth2 import id_token
from google.auth.transport import requests as greq
import os
app = Flask(__name__)
IAP_AUDIENCE = os.environ.get("IAP_OAUTH_CLIENT_ID","") # set this for IAP
def verify_google_id_token(tok:str)->dict:
return id_token.verify_oauth2_token(tok, greq.Request(), audience=IAP_AUDIENCE)
@app.post("/run-secure-endpoint")
def run_secure():
auth = request.headers.get("Authorization","")
if not auth.startswith("Bearer "): return jsonify({"error":"missing bearer"}), 401
try: payload = verify_google_id_token(auth.split()[1])
except Exception as e: return jsonify({"error":"invalid token","detail":str(e)}), 401
return jsonify({"ok": True, "email": payload.get("email")})
Common gotchas (and fixes)
-
401 with IAP: wrong audience. Use the IAP OAuth Client ID as
aud
when verifying. -
Empty
id_token
: check your OAuth2 client type (Web) and the redirect URI fromgetRedirectURL()
. - Tokens expiring mid‑session: cache with a small buffer (5 minutes) and refresh on demand.
-
CORS: add
Access-Control-Allow-Origin
+Authorization
header allowances if you call from content scripts or pages.
Minimal repo layout
repo/
├─ extension/ (manifest.json, background.js, popup.{html,js})
└─ backend/ (main.py, requirements.txt, Dockerfile)
That’s it—short and sweet. If you need a deeper walkthrough, look for my longer guide or case‑study later.
Top comments (1)
Nice Article Sir!