The below code is a Postman Pre-Request Script that is installed on your API collection that contains the routes you are testing. Its purpose is to convert static credentials, like a email-password combination or service account key into an access token to be used with API calls.
Emulate User
To use it for testing on behalf of users, you would add a X-Auth-Token-Type: user
header on the request (used & removed by the script below) and you will need to have set up the following environment variables:
Name |
Value |
firebase_apiKey |
The Firebase API Key for a web application |
firebase_test_user |
An email for an account used for testing |
firebase_test_password |
A password for an account used for testing |
Emulate Service Account (Use with caution!)
To use it for testing on behalf of a service account, you would add a X-Auth-Token-Type: admin
header on the request (used & removed by the script below) and you will need to have set up the following environment variables:
Name |
Value |
firebase_privateKey |
The value of private_key in a Service Account Key Important: For security do not set the "initial value" for this variable! |
firebase_scope (optional) |
A space-delimited list of scopes to authenticate for. Note: If omitted, the default Admin SDK scopes are used |
The Pre-Request Script
const { Header, Response, HeaderList } = require('postman-collection');
/**
* Information about the current Firebase user
* @typedef {Object} UserInfo
* @property {String} accessToken - The Firebase ID token for this user
* @property {String | undefined} displayName - Display name of the user, if available
* @property {Number} expiresAt - When this token expires as a unix timestamp
* @property {String | undefined} email - Email associated with the user, if available
* @property {String} refreshToken - Refresh token for this user's ID token
* @property {String} uid - User ID for this user
*/
/**
* Loads a third-party JavaScript module from a CDN (e.g. unpkg, jsDelivr)
* @param {[String, String, String]} moduleTuple - Array containing the module's ID, its source URL and an optional SHA256 signature
* @param {Object | (err: any, exports: any) => any} exportsRefOrCallback - Object reference to use as `exports` for the module or a result handler callback
* @param {(err: any, exports: any) => any} callback - result handler callback
*/
function loadModule(moduleTuple, exportsRefOrCallback, callback = undefined) {
const exports = arguments.length == 2 ? {} : exportsRefOrCallback;
callback = arguments.length == 2 ? exportsRefOrCallback : callback;
const [id, src, signature] = moduleTuple;
if (pm.environment.has("jslibcache_" + id)) {
const script = pm.environment.get("jslibcache_" + id);
if (signature && signature === CryptoJS.SHA256(script).toString()) {
console.log("Using cached copy of " + src);
try {
eval(script);
return callback(null, exports);
} catch {}
}
}
pm.sendRequest(src, (err, response) => {
try {
if (err || response.code !== 200) {
pm.expect.fail('Could not load external library');
}
const script = response.text();
signature && pm.expect(CryptoJS.SHA256(script).toString(), 'External library (' + id + ') has a bad SHA256 signature').to.equal(signature);
pm.environment.set("jslibcache_" + id, script);
eval(script);
callback(null, exports);
} catch (err) {
callback(err, null);
}
});
}
/**
* Signs in a test user using an email and password combination
*
* @param {String} email email of the account to sign in with
* @param {String} password email of the account to sign in with
* @param {(error: any, response: Response) => any} callback request result handler
*/
function signInWithEmailAndPassword(email, password, callback) {
pm.sendRequest({
url: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=" + encodeURIComponent(pm.environment.get("firebase_apiKey")),
body: JSON.stringify({ email, password, "returnSecureToken": true }),
headers: new HeaderList({}, [new Header("application/json", "Content-Type")]),
method: "POST"
}, callback);
}
/**
* Builds an Admin SDK compatible JWT using a Service Account key
*
* Required Environment Variables:
* - `firebase_privateKey` - the private key from inside a service account key JSON file
*
* Environment Variables:
* - `firebase_scope` - scopes used for the access token, space delimited
*
* @param {Boolean | (error: any, idToken: String) => any} callbackOrForceRefresh token result handler or `true` to force using a fresh user token
* @param {(error: any, idToken: String) => any} [callback] token result handler
*/
function getAdminToken(callbackOrForceRefresh, callback) {
let forceRefresh = Boolean(callbackOrForceRefresh);
if (arguments.length === 1) {
callback = callbackOrForceRefresh;
forceRefresh = callbackOrForceRefresh = false;
}
loadModule(
["jsrsasign", "https://unpkg.com/jsrsasign@10.3.0/lib/jsrsasign.js", "39b7a00e9eed7d20b2e60fff0775697ff43160e02e5276868ae8780295598fd3"],
(loadErr, { KJUR }) => {
if (loadErr) return callback(loadErr, null);
const exp = pm.environment.get("currentAdmin.exp");
const nowSecs = Math.floor(Date.now() / 1000);
if (exp && exp > nowSecs && forceRefresh === false) {
return callback(null, pm.environment.get("currentAdmin.jwt"));
}
try {
if (!pm.environment.has('firebase_privateKey')) {
pm.expect.fail('Missing required environment variable "firebase_privateKey".');
}
// use specified scopes, or fallback to Admin SDK defaults
const scope = pm.environment.get('firebase_scope') || 'https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/firebase.database https://www.googleapis.com/auth/firebase.messaging https://www.googleapis.com/auth/identitytoolkit https://www.googleapis.com/auth/userinfo.email';
const privateKey = String(pm.environment.get('firebase_privateKey')).replace("\\n", "\n");
const header = {"alg" : "RS256", "typ" : "JWT"};
const claimSet =
{
"iss": "https://securetoken.google.com/" + pm.environment.get("firebase_projectId"),
"scope": scope,
"aud":"https://accounts.google.com/o/oauth2/auth",
"exp": nowSecs + 3600, // now + 1 hour
"iat": nowSecs
}
const jwt = KJUR.jws.JWS.sign(null, header, claimSet, privateKey);
// comment these lines out to disable caching
pm.environment.set("currentAdmin.jwt", jwt);
pm.environment.set("currentAdmin.exp", claimSet.exp);
callback(null, jwt);
} catch (err) {
callback(err, null);
}
}
);
}
/**
* Builds a User ID Token using an email-password combo
*
* Required Environment Variables:
* - `firebase_apiKey` - the Firebase API key for a web application
* - `firebase_test_user` - an email for a test user
* - `firebase_test_password` - the password for the test user
*
* @param {Boolean | (error: any, idToken: String) => any} callbackOrForceRefresh token result handler or `true` to force using a fresh user token
* @param {(error: any, idToken: String) => any} [callback] token result handler
*/
function getIdToken(callbackOrForceRefresh, callback) {
let forceRefresh = Boolean(callbackOrForceRefresh);
if (arguments.length === 1) {
callback = callbackOrForceRefresh;
forceRefresh = callbackOrForceRefresh = false;
}
if (pm.environment.has("currentUser") && forceRefresh === false) {
/** @type UserInfo */
const currentUser = JSON.parse(pm.environment.has("currentUser"));
if (currentUser.expiresAt > Date.now()) { // has token expired?
return callback(null, currentUser.accessToken);
}
}
try {
if (!pm.environment.has('firebase_apiKey')) {
pm.expect.fail('Missing required environment variable "firebase_apiKey".');
}
if (!pm.environment.has('firebase_test_user')) {
pm.expect.fail('Missing required environment variable "firebase_test_user".');
}
if (!pm.environment.has('firebase_test_password')) {
pm.expect.fail('Missing required environment variable "firebase_test_password".');
}
} catch (err) {
return callback(err, null);
}
signInWithEmailAndPassword(pm.environment.get("firebase_test_user"), pm.environment.get("firebase_test_password"), (err, response) => {
if (err || response.code !== 200) {
pm.expect.fail('Could not sign in user: ' + response.json().error.message);
}
/** @type String */
let accessToken;
try {
const { idToken, refreshToken, email, displayName, localId: uid, expiresIn } = response.json();
accessToken = idToken;
const expiresAt = Date.now() + Number(expiresIn);
// comment these lines out to disable caching
pm.environment.set("currentUser", JSON.stringify({ accessToken, refreshToken, email, displayName, uid, expiresAt }));
// pm.environment.set("currentUser.accessToken", accessToken);
// pm.environment.set("currentUser.refreshToken", refreshToken);
// pm.environment.set("currentUser.email", email);
// pm.environment.set("currentUser.displayName", displayName);
// pm.environment.set("currentUser.uid", uid);
// pm.environment.set("currentUser.expiresAt", expiresAt);
} catch (err) {
return callback(err, null);
}
callback(null, accessToken);
});
}
const tokenTypeHeader = pm.request.headers.one("X-Auth-Token-Type");
pm.request.removeHeader("X-Auth-Token-Type");
switch (tokenTypeHeader && tokenTypeHeader.value.toLowerCase()) {
case "admin":
getAdminToken(false, (err, token) => {
if (err || !token) pm.expect.fail("failed to get admin SDK token for request: " + err.message);
pm.request.addHeader(new Header("Bearer " + token, "Authorization"));
});
break;
case "user":
getIdToken(false, (err, idToken) => {
if (err || !idToken) pm.expect.fail("failed to get user ID token for request: " + err.message);
pm.request.addHeader(new Header("Bearer " + idToken, "Authorization"));
});
break;
default:
break; // no auth, do nothing
}