UNPKG

14.6 kBJavaScriptView Raw
1/*! firebase-admin v10.0.0 */
2"use strict";
3/*!
4 * Copyright 2018 Google Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.createSessionCookieVerifier = exports.createIdTokenVerifier = exports.FirebaseTokenVerifier = exports.SESSION_COOKIE_INFO = exports.ID_TOKEN_INFO = void 0;
20var error_1 = require("../utils/error");
21var util = require("../utils/index");
22var validator = require("../utils/validator");
23var jwt_1 = require("../utils/jwt");
24// Audience to use for Firebase Auth Custom tokens
25var FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
26// URL containing the public keys for the Google certs (whose private keys are used to sign Firebase
27// Auth ID tokens)
28var CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
29// URL containing the public keys for Firebase session cookies. This will be updated to a different URL soon.
30var SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys';
31var EMULATOR_VERIFIER = new jwt_1.EmulatorSignatureVerifier();
32/**
33 * User facing token information related to the Firebase ID token.
34 *
35 * @internal
36 */
37exports.ID_TOKEN_INFO = {
38 url: 'https://firebase.google.com/docs/auth/admin/verify-id-tokens',
39 verifyApiName: 'verifyIdToken()',
40 jwtName: 'Firebase ID token',
41 shortName: 'ID token',
42 expiredErrorCode: error_1.AuthClientErrorCode.ID_TOKEN_EXPIRED,
43};
44/**
45 * User facing token information related to the Firebase session cookie.
46 *
47 * @internal
48 */
49exports.SESSION_COOKIE_INFO = {
50 url: 'https://firebase.google.com/docs/auth/admin/manage-cookies',
51 verifyApiName: 'verifySessionCookie()',
52 jwtName: 'Firebase session cookie',
53 shortName: 'session cookie',
54 expiredErrorCode: error_1.AuthClientErrorCode.SESSION_COOKIE_EXPIRED,
55};
56/**
57 * Class for verifying general purpose Firebase JWTs. This verifies ID tokens and session cookies.
58 *
59 * @internal
60 */
61var FirebaseTokenVerifier = /** @class */ (function () {
62 function FirebaseTokenVerifier(clientCertUrl, issuer, tokenInfo, app) {
63 this.issuer = issuer;
64 this.tokenInfo = tokenInfo;
65 this.app = app;
66 if (!validator.isURL(clientCertUrl)) {
67 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided public client certificate URL is an invalid URL.');
68 }
69 else if (!validator.isURL(issuer)) {
70 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided JWT issuer is an invalid URL.');
71 }
72 else if (!validator.isNonNullObject(tokenInfo)) {
73 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided JWT information is not an object or null.');
74 }
75 else if (!validator.isURL(tokenInfo.url)) {
76 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The provided JWT verification documentation URL is invalid.');
77 }
78 else if (!validator.isNonEmptyString(tokenInfo.verifyApiName)) {
79 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT verify API name must be a non-empty string.');
80 }
81 else if (!validator.isNonEmptyString(tokenInfo.jwtName)) {
82 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT public full name must be a non-empty string.');
83 }
84 else if (!validator.isNonEmptyString(tokenInfo.shortName)) {
85 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT public short name must be a non-empty string.');
86 }
87 else if (!validator.isNonNullObject(tokenInfo.expiredErrorCode) || !('code' in tokenInfo.expiredErrorCode)) {
88 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'The JWT expiration error code must be a non-null ErrorInfo object.');
89 }
90 this.shortNameArticle = tokenInfo.shortName.charAt(0).match(/[aeiou]/i) ? 'an' : 'a';
91 this.signatureVerifier =
92 jwt_1.PublicKeySignatureVerifier.withCertificateUrl(clientCertUrl, app.options.httpAgent);
93 // For backward compatibility, the project ID is validated in the verification call.
94 }
95 /**
96 * Verifies the format and signature of a Firebase Auth JWT token.
97 *
98 * @param jwtToken - The Firebase Auth JWT token to verify.
99 * @param isEmulator - Whether to accept Auth Emulator tokens.
100 * @returns A promise fulfilled with the decoded claims of the Firebase Auth ID token.
101 */
102 FirebaseTokenVerifier.prototype.verifyJWT = function (jwtToken, isEmulator) {
103 var _this = this;
104 if (isEmulator === void 0) { isEmulator = false; }
105 if (!validator.isString(jwtToken)) {
106 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "First argument to " + this.tokenInfo.verifyApiName + " must be a " + this.tokenInfo.jwtName + " string.");
107 }
108 return this.ensureProjectId()
109 .then(function (projectId) {
110 return _this.decodeAndVerify(jwtToken, projectId, isEmulator);
111 })
112 .then(function (decoded) {
113 var decodedIdToken = decoded.payload;
114 decodedIdToken.uid = decodedIdToken.sub;
115 return decodedIdToken;
116 });
117 };
118 FirebaseTokenVerifier.prototype.ensureProjectId = function () {
119 var _this = this;
120 return util.findProjectId(this.app)
121 .then(function (projectId) {
122 if (!validator.isNonEmptyString(projectId)) {
123 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'Must initialize app with a cert credential or set your Firebase project ID as the ' +
124 ("GOOGLE_CLOUD_PROJECT environment variable to call " + _this.tokenInfo.verifyApiName + "."));
125 }
126 return Promise.resolve(projectId);
127 });
128 };
129 FirebaseTokenVerifier.prototype.decodeAndVerify = function (token, projectId, isEmulator) {
130 var _this = this;
131 return this.safeDecode(token)
132 .then(function (decodedToken) {
133 _this.verifyContent(decodedToken, projectId, isEmulator);
134 return _this.verifySignature(token, isEmulator)
135 .then(function () { return decodedToken; });
136 });
137 };
138 FirebaseTokenVerifier.prototype.safeDecode = function (jwtToken) {
139 var _this = this;
140 return jwt_1.decodeJwt(jwtToken)
141 .catch(function (err) {
142 if (err.code == jwt_1.JwtErrorCode.INVALID_ARGUMENT) {
143 var verifyJwtTokenDocsMessage = " See " + _this.tokenInfo.url + " " +
144 ("for details on how to retrieve " + _this.shortNameArticle + " " + _this.tokenInfo.shortName + ".");
145 var errorMessage = "Decoding " + _this.tokenInfo.jwtName + " failed. Make sure you passed " +
146 ("the entire string JWT which represents " + _this.shortNameArticle + " ") +
147 (_this.tokenInfo.shortName + ".") + verifyJwtTokenDocsMessage;
148 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
149 }
150 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, err.message);
151 });
152 };
153 /**
154 * Verifies the content of a Firebase Auth JWT.
155 *
156 * @param fullDecodedToken - The decoded JWT.
157 * @param projectId - The Firebase Project Id.
158 * @param isEmulator - Whether the token is an Emulator token.
159 */
160 FirebaseTokenVerifier.prototype.verifyContent = function (fullDecodedToken, projectId, isEmulator) {
161 var header = fullDecodedToken && fullDecodedToken.header;
162 var payload = fullDecodedToken && fullDecodedToken.payload;
163 var projectIdMatchMessage = " Make sure the " + this.tokenInfo.shortName + " comes from the same " +
164 'Firebase project as the service account used to authenticate this SDK.';
165 var verifyJwtTokenDocsMessage = " See " + this.tokenInfo.url + " " +
166 ("for details on how to retrieve " + this.shortNameArticle + " " + this.tokenInfo.shortName + ".");
167 var errorMessage;
168 if (!isEmulator && typeof header.kid === 'undefined') {
169 var isCustomToken = (payload.aud === FIREBASE_AUDIENCE);
170 var isLegacyCustomToken = (header.alg === 'HS256' && payload.v === 0 && 'd' in payload && 'uid' in payload.d);
171 if (isCustomToken) {
172 errorMessage = this.tokenInfo.verifyApiName + " expects " + this.shortNameArticle + " " +
173 (this.tokenInfo.shortName + ", but was given a custom token.");
174 }
175 else if (isLegacyCustomToken) {
176 errorMessage = this.tokenInfo.verifyApiName + " expects " + this.shortNameArticle + " " +
177 (this.tokenInfo.shortName + ", but was given a legacy custom token.");
178 }
179 else {
180 errorMessage = 'Firebase ID token has no "kid" claim.';
181 }
182 errorMessage += verifyJwtTokenDocsMessage;
183 }
184 else if (!isEmulator && header.alg !== jwt_1.ALGORITHM_RS256) {
185 errorMessage = this.tokenInfo.jwtName + " has incorrect algorithm. Expected \"" + jwt_1.ALGORITHM_RS256 + '" but got ' +
186 '"' + header.alg + '".' + verifyJwtTokenDocsMessage;
187 }
188 else if (payload.aud !== projectId) {
189 errorMessage = this.tokenInfo.jwtName + " has incorrect \"aud\" (audience) claim. Expected \"" +
190 projectId + '" but got "' + payload.aud + '".' + projectIdMatchMessage +
191 verifyJwtTokenDocsMessage;
192 }
193 else if (payload.iss !== this.issuer + projectId) {
194 errorMessage = this.tokenInfo.jwtName + " has incorrect \"iss\" (issuer) claim. Expected " +
195 ("\"" + this.issuer) + projectId + '" but got "' +
196 payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage;
197 }
198 else if (typeof payload.sub !== 'string') {
199 errorMessage = this.tokenInfo.jwtName + " has no \"sub\" (subject) claim." + verifyJwtTokenDocsMessage;
200 }
201 else if (payload.sub === '') {
202 errorMessage = this.tokenInfo.jwtName + " has an empty string \"sub\" (subject) claim." + verifyJwtTokenDocsMessage;
203 }
204 else if (payload.sub.length > 128) {
205 errorMessage = this.tokenInfo.jwtName + " has \"sub\" (subject) claim longer than 128 characters." +
206 verifyJwtTokenDocsMessage;
207 }
208 if (errorMessage) {
209 throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
210 }
211 };
212 FirebaseTokenVerifier.prototype.verifySignature = function (jwtToken, isEmulator) {
213 var _this = this;
214 var verifier = isEmulator ? EMULATOR_VERIFIER : this.signatureVerifier;
215 return verifier.verify(jwtToken)
216 .catch(function (error) {
217 throw _this.mapJwtErrorToAuthError(error);
218 });
219 };
220 /**
221 * Maps JwtError to FirebaseAuthError
222 *
223 * @param error - JwtError to be mapped.
224 * @returns FirebaseAuthError or Error instance.
225 */
226 FirebaseTokenVerifier.prototype.mapJwtErrorToAuthError = function (error) {
227 var verifyJwtTokenDocsMessage = " See " + this.tokenInfo.url + " " +
228 ("for details on how to retrieve " + this.shortNameArticle + " " + this.tokenInfo.shortName + ".");
229 if (error.code === jwt_1.JwtErrorCode.TOKEN_EXPIRED) {
230 var errorMessage = this.tokenInfo.jwtName + " has expired. Get a fresh " + this.tokenInfo.shortName +
231 (" from your client app and try again (auth/" + this.tokenInfo.expiredErrorCode.code + ").") +
232 verifyJwtTokenDocsMessage;
233 return new error_1.FirebaseAuthError(this.tokenInfo.expiredErrorCode, errorMessage);
234 }
235 else if (error.code === jwt_1.JwtErrorCode.INVALID_SIGNATURE) {
236 var errorMessage = this.tokenInfo.jwtName + " has invalid signature." + verifyJwtTokenDocsMessage;
237 return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
238 }
239 else if (error.code === jwt_1.JwtErrorCode.NO_MATCHING_KID) {
240 var errorMessage = this.tokenInfo.jwtName + " has \"kid\" claim which does not " +
241 ("correspond to a known public key. Most likely the " + this.tokenInfo.shortName + " ") +
242 'is expired, so get a fresh token from your client app and try again.';
243 return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
244 }
245 return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, error.message);
246 };
247 return FirebaseTokenVerifier;
248}());
249exports.FirebaseTokenVerifier = FirebaseTokenVerifier;
250/**
251 * Creates a new FirebaseTokenVerifier to verify Firebase ID tokens.
252 *
253 * @internal
254 * @param app - Firebase app instance.
255 * @returns FirebaseTokenVerifier
256 */
257function createIdTokenVerifier(app) {
258 return new FirebaseTokenVerifier(CLIENT_CERT_URL, 'https://securetoken.google.com/', exports.ID_TOKEN_INFO, app);
259}
260exports.createIdTokenVerifier = createIdTokenVerifier;
261/**
262 * Creates a new FirebaseTokenVerifier to verify Firebase session cookies.
263 *
264 * @internal
265 * @param app - Firebase app instance.
266 * @returns FirebaseTokenVerifier
267 */
268function createSessionCookieVerifier(app) {
269 return new FirebaseTokenVerifier(SESSION_COOKIE_CERT_URL, 'https://session.firebase.google.com/', exports.SESSION_COOKIE_INFO, app);
270}
271exports.createSessionCookieVerifier = createSessionCookieVerifier;