-
-
Notifications
You must be signed in to change notification settings - Fork 11
Open
Description
I was hoping your library would support verifying a Google Login token as described here https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
It was looking a different endpoint to verify Google's keys, and it was expecting a different audience.
I've modified your code and pieced it together to do just what I need. Feel free to incorporate these changes in your library in a way that doesn't break your other use cases:
import { decodeProtectedHeader, jwtVerify } from "jose";
import { importX509 } from "jose";
const inFlight = new Map();
const cache = new Map();
const canUseDefaultCache =
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
typeof globalThis.caches?.default?.put === "function";
/**
* Imports a public key for the provided Google Cloud (GCP)
* service account credentials.
*
* @throws {FetchError} - If the X.509 certificate could not be fetched.
*/
async function importPublicKey(options) {
const keyId = options.keyId;
const certificateURL = options.certificateURL ?? "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"; // prettier-ignore
const cacheKey = `${certificateURL}?key=${keyId}`;
const value = cache.get(cacheKey);
const now = Date.now();
async function fetchKey() {
// Fetch the public key from Google's servers
const res = await fetch(certificateURL);
if (!res.ok) {
const error = await res
.json()
.then((data) => data.error.message)
.catch(() => undefined);
throw new FetchError(error ?? "Failed to fetch the public key", {
response: res,
});
}
const data = await res.json();
const x509 = data[keyId];
if (!x509) {
throw new Error(`Public key "${keyId}" not found.`);
}
const key = await importX509(x509, "RS256");
// Resolve the expiration time of the key
const maxAge = res.headers.get("cache-control")?.match(/max-age=(\d+)/)?.[1]; // prettier-ignore
const expires = Date.now() + Number(maxAge ?? "3600") * 1000;
// Update the local cache
cache.set(cacheKey, { key, expires });
inFlight.delete(keyId);
return key;
}
// Attempt to read the key from the local cache
if (value) {
if (value.expires > now + 10_000) {
// If the key is about to expire, start a new request in the background
if (value.expires - now < 600_000) {
const promise = fetchKey();
inFlight.set(cacheKey, promise);
if (options.waitUntil) {
options.waitUntil(promise);
}
}
return value.key;
}
else {
cache.delete(cacheKey);
}
}
// Check if there is an in-flight request for the same key ID
let promise = inFlight.get(cacheKey);
// If not, start a new request
if (!promise) {
promise = fetchKey();
inFlight.set(cacheKey, promise);
}
return await promise;
}
// based on https://www.npmjs.com/package/web-auth-library?activeTab=code
// made to check per Google's recommendations: https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
export async function verifyIdToken(options) {
if (!options?.idToken) {
throw new TypeError(`Missing "idToken"`);
}
let clientId = options?.clientId;
if (clientId === undefined) {
throw new TypeError(`Missing "clientId"`);
}
if (!options.waitUntil && canUseDefaultCache) {
console.warn("Missing `waitUntil` option.")
}
// Import the public key from the Google Cloud project
const header = decodeProtectedHeader(options.idToken);
const now = Math.floor(Date.now() / 1000);
const key = await importPublicKey({
keyId: header.kid,
certificateURL: "https://www.googleapis.com/oauth2/v1/certs",
waitUntil: options.waitUntil,
});
const { payload } = await jwtVerify(options.idToken, key, {
audience: clientId,
issuer: ['https://accounts.google.com','accounts.google.com'],
maxTokenAge: "1h",
clockTolerance: '5m'
});
if (!payload.sub) {
throw new Error(`Missing "sub" claim`);
}
if (typeof payload.auth_time === "number" && payload.auth_time > now) {
throw new Error(`Unexpected "auth_time" claim value`);
}
return payload;
}
Used like so:
let decoded = await verifyIdToken({
idToken: googleLoginToken,
clientId: env.GOOGLE_CLIENT_ID,
waitUntil: context.waitUntil
})
sykezztowfiqi, jiahui92, winrey and sykezzsykezzkoistya, iSnakeBuzz, craigsc, jamcoupe, gkiely and 4 more
Metadata
Metadata
Assignees
Labels
No labels