Giới thiệu
Trong bài viết này, chúng ta sẽ khám phá cách gọi một API Cloud Run được bảo vệ bằng IAP (Identity-Aware Proxy) từ một Chrome Extension sử dụng Manifest V3 (MV3). Bài viết không chỉ hướng dẫn bạn từng bước để thực hiện mà còn chỉ ra những cạm bẫy phổ biến mà bạn có thể gặp phải trong quá trình triển khai.
Mục tiêu
- Sử dụng
chrome.identity.launchWebAuthFlowđể lấy Google ID token. - Gửi token dưới dạng
Authorization: Bearer <token>đến endpoint Cloud Run được bảo vệ bằng IAP/IAM. - Xác minh audience của token trên server và lưu cache token với một khoảng thời gian hết hạn nhỏ trên client.
Tóm tắt
1. Lấy ID token (MV3)
Để bắt đầu, bạn cần một tệp background.js chứa mã sau:
javascript
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 (tùy chọn)
Để cải thiện hiệu suất, bạn có thể thêm mã sau để lưu trữ token:
javascript
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. Gọi API
Tiếp theo, bạn cần mã trong tệp popup.js để gọi API:
javascript
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());
});
Và trong background.js:
javascript
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. Xác minh trên Flask (Cloud Run)
Cuối cùng, mã xác minh token sẽ nằm trong tệp main.py:
python
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")})
Những cạm bẫy phổ biến và cách khắc phục
- Lỗi 401 với IAP: Kiểm tra audience. Sử dụng IAP OAuth Client ID làm
audkhi xác minh. - Token
id_tokenrỗng: Kiểm tra loại client OAuth2 (Web) và redirect URI từgetRedirectURL(). - Token hết hạn giữa phiên: Lưu cache với một khoảng đệm nhỏ (5 phút) và làm mới khi cần.
- CORS: Thêm
Access-Control-Allow-Originvà cho phép headerAuthorizationnếu bạn gọi từ script hoặc trang nội dung.
Cấu trúc repo tối thiểu
repo/
├─ extension/ (manifest.json, background.js, popup.{html,js})
└─ backend/ (main.py, requirements.txt, Dockerfile)
Kết luận
Bài viết này đã hướng dẫn bạn cách gọi API Cloud Run được bảo vệ bằng IAP từ Chrome Extension. Hy vọng rằng những thông tin và mã nguồn cung cấp sẽ giúp bạn vượt qua những cạm bẫy phổ biến. Nếu bạn cần hướng dẫn chi tiết hơn, hãy tìm kiếm bài viết hoặc case-study dài hơn của tôi sau này. Hãy bắt đầu ngay hôm nay và thực hiện dự án của bạn!
Thực hành tốt nhất và mẹo hiệu suất
- Luôn xác minh audience của ID token trước khi sử dụng.
- Sử dụng cache cho token để cải thiện hiệu suất và trải nghiệm người dùng.
- Kiểm tra và xử lý các lỗi CORS để đảm bảo API hoạt động mượt mà từ các nguồn khác nhau.
Câu hỏi thường gặp (FAQ)
H: Làm thế nào để tôi lấy ID token?
Đ: Bạn có thể sử dụng hàm getIdToken() trong background.js để lấy ID token từ Google.
H: Tại sao tôi nhận được lỗi 401?
Đ: Lỗi 401 thường xảy ra do audience không đúng. Đảm bảo rằng bạn đã sử dụng IAP OAuth Client ID thích hợp.