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!