sample/app/server/verify-iap-jwt.js (81 lines of code) (raw):

/* * Copyright 2019 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ const jwt = require('jsonwebtoken'); const request = require('request'); /** IAP signed JWT algorithm. */ const ALGORITHM = 'ES256'; /** IAP signed JWT issuer URL. */ const ISSUER = 'https://cloud.google.com/iap'; /** IAP public keys URL. */ const PUBLIC_KEY_URL = 'https://www.gstatic.com/iap/verify/public_key'; /** Defines the IAP JWT verifier. */ class IapJwtVerifier { /** * Initializes an IAP JWT verifier. * @param {string} projectId * @param {string} projectNumber * @constructor */ constructor(projectId, projectNumber) { /** @private {string} The project ID. */ this.projectId = projectId; /** @private {string} The project number. */ this.projectNumber = projectNumber; }; /** * Verifies the IAP JWT. * @param {string} jwtToken The signed IAP JWT token to verify. * @return {Promise<!Object>} The decoded payload of the verified JWT. */ verify(jwtToken) { let header; let payload; return Promise.resolve().then(() => { // For GAE: /projects/PROJECT_NUMBER/apps/PROJECT_ID const aud = `/projects/${this.projectNumber}/apps/${this.projectId}`; const fullDecodedToken = jwt.decode(jwtToken, { complete: true, }); header = fullDecodedToken && fullDecodedToken.header; payload = fullDecodedToken && fullDecodedToken.payload; if (!fullDecodedToken) { throw new Error('Decoding the JWT failed.'); } else if (typeof header.kid === 'undefined') { throw new Error('IAP JWT has no "kid" claim.'); } else if (header.alg !== ALGORITHM) { throw new Error(`IAP JWT has incorrect algorithm. Expected ${ALGORITHM} algorithm but got ${header.alg}`); } else if (payload.aud !== aud) { throw new Error(`IAP JWT has incorrect audience. Expected ${aud} but got ${payload.aud}`); } else if (payload.iss !== ISSUER) { throw new Error(`IAP JWT has incorrect issuer. Expected ${ISSUER} algorithm but got ${payload.iss}`); } else if (typeof payload.sub !== 'string' || !payload.sub) { throw new Error('IAP JWT has no valid "sub".') } return this.fetchPublicKey(header.kid); }).then((publicKey) => { return this.verifyJwtSignatureWithKey(jwtToken, publicKey); }); } /** * @param {string} The kid whose public cert is to be returned. * @return {Promise<string>} A promise that resolves with the public key that the provided kid maps to. * @private */ fetchPublicKey(kid) { if (typeof this.publicKeys !== 'undefined' && this.publicKeys.hasOwnProperty(kid)) { return Promise.resolve(this.publicKeys[kid]); } return new Promise((resolve, reject) => { request({ url: PUBLIC_KEY_URL, json: true }, (error, response, body) => { if (!error && response.statusCode === 200) { // Cache public keys. this.publicKeys = body; if (this.publicKeys.hasOwnProperty(kid)) { // Return the corresponding key. resolve(body[kid]); } else { reject('IAP JWT has "kid" claim which does not correspond to a known public key.'); } } else { reject(error); } }); }); } /** * Verifies the IAP token signature using the provided key. * @param {string} jwtToken The IAP token. * @param {string} publicKey The corresponding public key. * @return {Promise<!Object>} A promise that resolves with the decoded JWT claims. * @private */ verifyJwtSignatureWithKey(jwtToken, publicKey) { return new Promise((resolve, reject) => { jwt.verify(jwtToken, publicKey, { algorithms: [ALGORITHM], }, (error, decodedToken) => { if (error) { if (error.name === 'TokenExpiredError') { return reject(new Error('IAP JWT is expired')); } else if (error.name === 'JsonWebTokenError') { return reject(new Error('IAP JWT has invalid signature')); } return reject(new Error(error.message)); } else { resolve(decodedToken); } }); }); } } module.exports = IapJwtVerifier;