chore: add nonce check and verifying jwt claims for google (#6614)

This commit is contained in:
Jonathan Samines
2025-12-08 15:49:31 -06:00
committed by github-actions[bot]
parent 312fc0248a
commit f758f14a4b

View File

@@ -1,5 +1,6 @@
import { betterFetch } from "@better-fetch/fetch";
import { decodeJwt } from "jose";
import { APIError } from "better-call";
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
import { logger } from "../env";
import { BetterAuthError } from "../error";
import type { OAuthProvider, ProviderOptions } from "../oauth2";
@@ -126,24 +127,26 @@ export const google = (options: GoogleOptions) => {
if (options.verifyIdToken) {
return options.verifyIdToken(token, nonce);
}
const googlePublicKeyUrl = `https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=${token}`;
const { data: tokenInfo } = await betterFetch<{
aud: string;
iss: string;
email: string;
email_verified: boolean;
name: string;
picture: string;
sub: string;
}>(googlePublicKeyUrl);
if (!tokenInfo) {
// Verify JWT integrity
// See https://developers.google.com/identity/sign-in/web/backend-auth#verify-the-integrity-of-the-id-token
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
if (!kid || !jwtAlg) return false;
const publicKey = await getGooglePublicKey(kid);
const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
algorithms: [jwtAlg],
issuer: ["https://accounts.google.com", "accounts.google.com"],
audience: options.clientId,
maxTokenAge: "1h",
});
if (nonce && jwtClaims.nonce !== nonce) {
return false;
}
const isValid =
tokenInfo.aud === options.clientId &&
(tokenInfo.iss === "https://accounts.google.com" ||
tokenInfo.iss === "accounts.google.com");
return isValid;
return true;
},
async getUserInfo(token) {
if (options.getUserInfo) {
@@ -169,3 +172,29 @@ export const google = (options: GoogleOptions) => {
options,
} satisfies OAuthProvider<GoogleProfile>;
};
export const getGooglePublicKey = async (kid: string) => {
const { data } = await betterFetch<{
keys: Array<{
kid: string;
alg: string;
kty: string;
use: string;
n: string;
e: string;
}>;
}>("https://www.googleapis.com/oauth2/v3/certs");
if (!data?.keys) {
throw new APIError("BAD_REQUEST", {
message: "Keys not found",
});
}
const jwk = data.keys.find((key) => key.kid === kid);
if (!jwk) {
throw new Error(`JWK with kid ${kid} not found`);
}
return await importJWK(jwk, jwk.alg);
};