@pedwise/next-firebase-auth-edge
Version:
Next.js 13 Firebase Authentication for Edge and server runtimes. Dedicated for Next 13 server components. Compatible with Next.js middleware.
179 lines • 9.48 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createIdTokenVerifier = exports.FirebaseTokenVerifier = void 0;
const firebase_1 = require("./firebase");
const signature_verifier_1 = require("./signature-verifier");
const validator_1 = require("./validator");
const error_1 = require("./error");
const error_2 = require("./jwt/error");
const EMULATOR_VERIFIER = new signature_verifier_1.EmulatorSignatureVerifier();
class FirebaseTokenVerifier {
constructor(clientCertUrl, issuer, tokenInfo, projectId) {
this.issuer = issuer;
this.tokenInfo = tokenInfo;
this.projectId = projectId;
if (!(0, validator_1.isURL)(clientCertUrl)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided public client certificate URL is an invalid URL.");
}
else if (!(0, validator_1.isURL)(issuer)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided JWT issuer is an invalid URL.");
}
else if (!(0, validator_1.isNonNullObject)(tokenInfo)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided JWT information is not an object or null.");
}
else if (!(0, validator_1.isURL)(tokenInfo.url)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The provided JWT verification documentation URL is invalid.");
}
else if (!(0, validator_1.isNonEmptyString)(tokenInfo.verifyApiName)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT verify API name must be a non-empty string.");
}
else if (!(0, validator_1.isNonEmptyString)(tokenInfo.jwtName)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT public full name must be a non-empty string.");
}
else if (!(0, validator_1.isNonEmptyString)(tokenInfo.shortName)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT public short name must be a non-empty string.");
}
else if (!(0, validator_1.isNonNullObject)(tokenInfo.expiredErrorCode) ||
!("code" in tokenInfo.expiredErrorCode)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "The JWT expiration error code must be a non-null ErrorInfo object.");
}
this.shortNameArticle = tokenInfo.shortName.charAt(0).match(/[aeiou]/i)
? "an"
: "a";
this.signatureVerifier =
signature_verifier_1.PublicKeySignatureVerifier.withCertificateUrl(clientCertUrl);
}
async verifyJWT(jwtToken, isEmulator = false) {
if (!(0, validator_1.isString)(jwtToken)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, `First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.`);
}
const decoded = await this.decodeAndVerify(jwtToken, this.projectId, isEmulator);
const decodedIdToken = decoded.payload;
decodedIdToken.uid = decodedIdToken.sub;
return decodedIdToken;
}
decodeAndVerify(token, projectId, isEmulator, audience) {
return this.safeDecode(token).then((decodedToken) => {
this.verifyContent(decodedToken, projectId, isEmulator, audience);
return this.verifySignature(token, isEmulator).then(() => decodedToken);
});
}
safeDecode(jwtToken) {
return (0, signature_verifier_1.decodeJwt)(jwtToken).catch((err) => {
if (err.code == error_2.JwtErrorCode.INVALID_ARGUMENT) {
const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` +
`for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`;
const errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed ` +
`the entire string JWT which represents ${this.shortNameArticle} ` +
`${this.tokenInfo.shortName}.` +
verifyJwtTokenDocsMessage;
throw error_1.FirebaseAuthError.toAuthErrorWithStack(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage, err);
}
throw error_1.FirebaseAuthError.toAuthErrorWithStack(error_1.AuthClientErrorCode.INTERNAL_ERROR, err.message, err);
});
}
verifyContent(fullDecodedToken, projectId, isEmulator, audience) {
const header = fullDecodedToken && fullDecodedToken.header;
const payload = fullDecodedToken && fullDecodedToken.payload;
const projectIdMatchMessage = ` Make sure the ${this.tokenInfo.shortName} comes from the same ` +
"Firebase project as the service account used to authenticate this SDK.";
const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` +
`for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`;
let errorMessage;
if (!isEmulator && typeof header.kid === "undefined") {
const isCustomToken = payload.aud === firebase_1.FIREBASE_AUDIENCE;
const isLegacyCustomToken = header.alg === "HS256" &&
payload.v === 0 &&
"d" in payload &&
"uid" in payload.d;
if (isCustomToken) {
errorMessage =
`${this.tokenInfo.verifyApiName} expects ${this.shortNameArticle} ` +
`${this.tokenInfo.shortName}, but was given a custom token.`;
}
else if (isLegacyCustomToken) {
errorMessage =
`${this.tokenInfo.verifyApiName} expects ${this.shortNameArticle} ` +
`${this.tokenInfo.shortName}, but was given a legacy custom token.`;
}
else {
errorMessage = `${this.tokenInfo.jwtName} has no "kid" claim.`;
}
errorMessage += verifyJwtTokenDocsMessage;
}
else if (!isEmulator && header.alg !== signature_verifier_1.ALGORITHM_RS256) {
errorMessage =
`${this.tokenInfo.jwtName} has incorrect algorithm. Expected "` +
signature_verifier_1.ALGORITHM_RS256 +
'" but got ' +
'"' +
header.alg +
'".' +
verifyJwtTokenDocsMessage;
}
else if (typeof audience !== "undefined" &&
!payload.aud.includes(audience)) {
errorMessage =
`${this.tokenInfo.jwtName} has incorrect "aud" (audience) claim. Expected "` +
audience +
'" but got "' +
payload.aud +
'".' +
verifyJwtTokenDocsMessage;
}
else if (typeof audience === "undefined" && payload.aud !== projectId) {
errorMessage =
`${this.tokenInfo.jwtName} has incorrect "aud" (audience) claim. Expected "` +
projectId +
'" but got "' +
payload.aud +
'".' +
projectIdMatchMessage +
verifyJwtTokenDocsMessage;
}
else if (payload.iss !== this.issuer + projectId) {
errorMessage =
`${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` +
`"${this.issuer}` +
projectId +
'" but got "' +
payload.iss +
'".' +
projectIdMatchMessage +
verifyJwtTokenDocsMessage;
}
else if (typeof payload.sub !== "string") {
errorMessage =
`${this.tokenInfo.jwtName} has no "sub" (subject) claim.` +
verifyJwtTokenDocsMessage;
}
else if (payload.sub === "") {
errorMessage =
`${this.tokenInfo.jwtName} has an empty string "sub" (subject) claim.` +
verifyJwtTokenDocsMessage;
}
else if (payload.sub.length > 128) {
errorMessage =
`${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` +
verifyJwtTokenDocsMessage;
}
if (errorMessage) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
}
}
verifySignature(jwtToken, isEmulator) {
const verifier = isEmulator ? EMULATOR_VERIFIER : this.signatureVerifier;
return verifier.verify(jwtToken).catch((error) => {
throw this.mapJwtErrorToAuthError(error);
});
}
mapJwtErrorToAuthError(error) {
return error_1.FirebaseAuthError.fromJwtError(error, this.tokenInfo, this.shortNameArticle);
}
}
exports.FirebaseTokenVerifier = FirebaseTokenVerifier;
function createIdTokenVerifier(projectId) {
return new FirebaseTokenVerifier(firebase_1.CLIENT_CERT_URL, "https://securetoken.google.com/", firebase_1.ID_TOKEN_INFO, projectId);
}
exports.createIdTokenVerifier = createIdTokenVerifier;
//# sourceMappingURL=token-verifier.js.map