node-webtokens
Version:
Simple, opinionated implementation of JWS and JWE compact serialization
118 lines (105 loc) • 3.9 kB
JavaScript
const jws = require('./lib/jws.js');
const jwe = require('./lib/jwe.js');
const { responder } = require('./lib/common.js');
// ===== TOKEN GENERATION =====================================================
exports.generate = (alg, ...rest) => {
// alg, payload, key[, cb] or alg, payload, keystore, kid[, cb]
if (rest[0].constructor === Object) return jws.generate(alg, ...rest);
// alg, enc, payload, key[, cb] or alg, enc, payload, keystore, kid[, cb]
if (rest[1].constructor === Object) return jwe.generate(alg, ...rest);
// There is no payload object where expected
let idx = rest.length - 1;
if (idx > 1 && idx < 5 && typeof rest[idx] === 'function') {
rest[idx](error);
} else {
throw error;
}
}
// ===== TOKEN PARSING ========================================================
exports.parse = (token) => new ParsedToken(token);
function ParsedToken(token) {
this.parts = typeof token === 'string' ? token.split('.') : [];
if (this.parts.length === 3) {
this.type = 'JWS';
} else if (this.parts.length === 5) {
this.type = 'JWE';
} else {
this.error = { message: 'Invalid token' };
return;
}
try {
this.header = JSON.parse(Buffer.from(this.parts[0], 'base64'));
} catch (error) {
this.error = { message: `Non parsable header. ${error.message}` };
return;
}
if (this.type === 'JWS') {
// Parsing exposes payload for JWS only; for JWE happens at verify
try {
this.payload = JSON.parse(Buffer.from(this.parts[1], 'base64'));
} catch (error) {
this.error = { message: `Non parsable payload. ${error.message}` };
}
}
}
// ===== POST-PARSING UTLITIES ================================================
ParsedToken.prototype.setAlgorithmList = function(algList, encList) {
// algList is ignored if not string or array of strings
if (typeof algList === 'string') {
this.algList = [algList];
} else if (Array.isArray(algList)) {
this.algList = algList;
}
// encList is ignored if not string or array of strings
if (typeof encList === 'string') {
this.encList = [encList];
} else if (Array.isArray(encList)) {
this.encList = encList;
}
return this;
}
ParsedToken.prototype.setTokenLifetime = function(lifetime) {
// lifetime is ignored if not integer greater than 0
if (Number.isInteger(lifetime) && lifetime > 0) this.lifetime = lifetime;
return this;
}
ParsedToken.prototype.setAudience = function(audList) {
// audList is ignored if not string or array of strings
if (typeof audList === 'string') {
this.audList = [audList];
} else if (Array.isArray(audList)) {
this.audList = audList;
}
return this;
}
ParsedToken.prototype.setIssuer = function(issList) {
// issList is ignored if not string or array of strings
if (typeof issList === 'string') {
this.issList = [issList];
} else if (Array.isArray(issList)) {
this.issList = issList;
}
return this;
}
// ===== TOKEN VERIFICATION ===================================================
ParsedToken.prototype.verify = function(p0, cb) {
// key[, cb] or keystore[, cb]
cb = typeof cb === 'function' ? cb : undefined;
if (this.error) return responder(null, this, cb);
let key;
if (p0.constructor !== Object) {
key = p0;
} else if (this.header.kid === undefined) {
// Cannot extract key from keystore
this.error = { message: 'Missing kid claim in header' };
return responder(null, this, cb);
} else if (p0[this.header.kid] === undefined) {
// Key not found in keystore
this.error = { message: 'Key with id not found', kid: this.header.kid };
return responder(null, this, cb);
} else {
key = p0[this.header.kid];
}
let verify = this.type === 'JWS' ? jws.verify : jwe.verify;
return verify(this, key, cb);
}