Skip to content

Instantly share code, notes, and snippets.

@aliustaoglu
Last active October 13, 2018 08:07
Show Gist options
  • Save aliustaoglu/28c3b3c2ed217a03ebb775cf4006c10c to your computer and use it in GitHub Desktop.
Save aliustaoglu/28c3b3c2ed217a03ebb775cf4006c10c to your computer and use it in GitHub Desktop.
'use strict';
const nJwt = require('./njwt'); //https://www.npmjs.com/package/njwt
const signingKey = 'my super secret key';
exports.handler = function(event, context, callback) {
var token = event.authorizationToken || event.Authorization || event.token;
nJwt.verify(token, signingKey, function(err, verifiedJwt) {
if (err) {
callback('Unauthorized');
} else {
//console.log(verifiedJwt);
callback(null, generatePolicy('user', 'Allow', event.methodArn, verifiedJwt.body));
}
});
};
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource, verifiedJwt) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = verifiedJwt;
return authResponse;
};
// This is cloned from njwt as it's not a part of AWS Lambda and this project does not use Auth0
// So we need to add this library to our JWT processing functions
'use strict';
var util = require('util');
var uuid = require('uuid');
var crypto = require('crypto');
var properties = require('./njwtproperties.json');
var algCryptoMap = {
HS256: 'SHA256',
HS384: 'SHA384',
HS512: 'SHA512',
RS256: 'RSA-SHA256',
RS384: 'RSA-SHA384',
RS512: 'RSA-SHA512',
ES256: 'RSA-SHA256',
ES384: 'RSA-SHA384',
ES512: 'RSA-SHA512',
none: 'none'
};
var algTypeMap = {
HS256: 'hmac',
HS384: 'hmac',
HS512: 'hmac',
RS256: 'sign',
RS384: 'sign',
RS512: 'sign',
ES256: 'sign',
ES384: 'sign',
ES512: 'sign'
};
function nowEpochSeconds(){
return Math.floor(new Date().getTime()/1000);
}
function base64urlEncode(str) {
return new Buffer(str)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
function base64urlUnescape(str) {
str += new Array(5 - str.length % 4).join('=');
return str.replace(/\-/g, '+').replace(/_/g, '/');
}
function isSupportedAlg(alg){
return !!algCryptoMap[alg];
}
function handleError(cb,err,value){
if(typeof cb==='function'){
return process.nextTick(function() {
cb(err,value);
});
}else if(err){
throw err;
}else{
return value;
}
}
function defaultKeyResolver(kid, cb) {
return cb(null, this.signingKey);
}
function JwtError(message) {
this.name = 'JwtError';
this.message = this.userMessage = message;
}
util.inherits(JwtError, Error);
function JwtParseError(message,jwtString,parsedHeader,parsedBody,innerError) {
this.name = 'JwtParseError';
this.message = this.userMessage = message;
this.jwtString = jwtString;
this.parsedHeader = parsedHeader;
this.parsedBody = parsedBody;
this.innerError = innerError;
}
util.inherits(JwtParseError, Error);
function JwtBody(claims){
if(!(this instanceof JwtBody)){
return new JwtBody(claims);
}
var self = this;
if(claims){
Object.keys(claims).forEach(function(k){
self[k] = claims[k];
});
}
return this;
}
JwtBody.prototype.toJSON = function() {
var self = this;
return Object.keys(self).reduce(function(acc,key){
acc[key] = self[key];
return acc;
},{});
};
JwtBody.prototype.compact = function compact(){
return base64urlEncode(JSON.stringify(this));
};
function JwtHeader(header){
if(!(this instanceof JwtHeader)){
return new JwtHeader(header);
}
var self = this;
this.typ = header && header.typ || 'JWT';
this.alg = header && header.alg || 'HS256';
if(header){
return Object.keys(header).reduce(function(acc,key){
if(self.reservedKeys.indexOf(key)===-1 && header.hasOwnProperty(key)){
acc[key] = header[key];
}
return acc;
},this);
}else{
return this;
}
}
JwtHeader.prototype.reservedKeys = ['typ','alg'];
JwtHeader.prototype.compact = function compact(){
return base64urlEncode(JSON.stringify(this));
};
function Jwt(claims, enforceDefaultFields){
if(!(this instanceof Jwt)){
return new Jwt(claims, enforceDefaultFields);
}
this.header = new JwtHeader();
this.body = new JwtBody(claims);
if (enforceDefaultFields !== false) {
this.setSigningAlgorithm('none');
if (!this.body.jti) {
this.setJti(uuid.v4());
}
if (!this.body.iat) {
this.setIssuedAt(nowEpochSeconds());
}
}
return this;
}
Jwt.prototype.setClaim = function setClaim(claim, value) {
this.body[claim] = value;
return this;
};
Jwt.prototype.setHeader = function setHeader(param, value) {
this.header[param] = value;
return this;
};
Jwt.prototype.setJti = function setJti(jti) {
this.body.jti = jti;
return this;
};
Jwt.prototype.setSubject = function setSubject(sub) {
this.body.sub = sub;
return this;
};
Jwt.prototype.setIssuer = function setIssuer(iss) {
this.body.iss = iss;
return this;
};
Jwt.prototype.setIssuedAt = function setIssuedAt(iat) {
this.body.iat = iat;
return this;
};
Jwt.prototype.setExpiration = function setExpiration(exp) {
if(exp){
this.body.exp = Math.floor((exp instanceof Date ? exp : new Date(exp)).getTime() / 1000);
}else{
delete this.body.exp;
}
return this;
};
Jwt.prototype.setNotBefore = function setNotBefore(nbf) {
if(nbf) {
this.body.nbf = Math.floor((nbf instanceof Date ? nbf : new Date(nbf)).getTime() / 1000);
} else {
delete this.body.nbf;
}
return this;
};
Jwt.prototype.setSigningKey = function setSigningKey(key) {
this.signingKey = key;
return this;
};
Jwt.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) {
if(!this.isSupportedAlg(alg)){
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
}
this.header.alg = alg;
return this;
};
Jwt.prototype.sign = function sign(payload, algorithm, cryptoInput) {
var buffer;
var signature;
var cryptoAlgName = algCryptoMap[algorithm];
var signingType = algTypeMap[algorithm];
if (!cryptoAlgName) {
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
}
if (signingType === 'hmac') {
buffer = crypto.createHmac(cryptoAlgName, cryptoInput).update(payload).digest();
} else {
buffer = crypto.createSign(cryptoAlgName).update(payload).sign(cryptoInput);
}
signature = base64urlEncode(buffer);
return signature;
};
Jwt.prototype.isSupportedAlg = isSupportedAlg;
Jwt.prototype.compact = function compact() {
var segments = [];
segments.push(this.header.compact());
segments.push(this.body.compact());
if(this.header.alg !== 'none'){
if (this.signingKey) {
this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey);
segments.push(this.signature);
}else{
throw new Error(properties.errors.SIGNING_KEY_REQUIRED);
}
}
return segments.join('.');
};
Jwt.prototype.toString = function(){
return this.compact();
};
Jwt.prototype.isExpired = function() {
return new Date(this.body.exp*1000) < new Date();
};
Jwt.prototype.isNotBefore = function() {
return new Date(this.body.nbf * 1000) >= new Date();
};
function Parser(options){
return this;
}
Parser.prototype.isSupportedAlg = isSupportedAlg;
Parser.prototype.safeJsonParse = function(input) {
var result;
try{
result = JSON.parse(new Buffer(base64urlUnescape(input),'base64'));
}catch(e){
return e;
}
return result;
};
Parser.prototype.parse = function parse(jwtString,cb){
var done = handleError.bind(null,cb);
var segments = jwtString.split('.');
var signature;
if(segments.length<2 || segments.length>3){
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null));
}
var header = this.safeJsonParse(segments[0]);
var body = this.safeJsonParse(segments[1]);
if(segments[2]){
signature = new Buffer(base64urlUnescape(segments[2]),'base64')
.toString('base64');
}
if(header instanceof Error){
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null,header));
}
if(body instanceof Error){
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,header,null,body));
}
var jwt = new Jwt(body, false);
jwt.setSigningAlgorithm(header.alg);
jwt.signature = signature;
jwt.verificationInput = segments[0] +'.' + segments[1];
jwt.header = new JwtHeader(header);
return done(null,jwt);
};
function Verifier(){
if(!(this instanceof Verifier)){
return new Verifier();
}
this.setSigningAlgorithm('HS256');
this.setKeyResolver(defaultKeyResolver.bind(this));
return this;
}
Verifier.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) {
if(!this.isSupportedAlg(alg)){
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
}
this.signingAlgorithm = alg;
return this;
};
Verifier.prototype.setSigningKey = function setSigningKey(keyStr) {
this.signingKey = keyStr;
return this;
};
Verifier.prototype.setKeyResolver = function setKeyResolver(keyResolver) {
this.keyResolver = keyResolver.bind(this);
};
Verifier.prototype.isSupportedAlg = isSupportedAlg;
Verifier.prototype.verify = function verify(jwtString,cb){
var jwt;
var done = handleError.bind(null,cb);
try {
jwt = new Parser().parse(jwtString);
} catch(e) {
return done(e);
}
var body = jwt.body;
var header = jwt.header;
var signature = jwt.signature;
var cryptoAlgName = algCryptoMap[header.alg];
var signingType = algTypeMap[header.alg];
if (header.alg !== this.signingAlgorithm) {
return done(new JwtParseError(properties.errors.SIGNATURE_ALGORITHM_MISMTACH,jwtString,header,body));
}
if (jwt.isExpired()) {
return done(new JwtParseError(properties.errors.EXPIRED,jwtString,header,body));
}
if (jwt.isNotBefore()) {
return done(new JwtParseError(properties.errors.NOT_ACTIVE,jwtString,header,body));
}
var digstInput = jwt.verificationInput;
var verified, digest;
return this.keyResolver(header.kid, function(err, signingKey) {
if (err) {
return done(new JwtParseError(util.format(properties.errors.KEY_RESOLVER_ERROR, header.kid),jwtString,header,body, err));
}
if( cryptoAlgName==='none') {
verified = true;
} else if(signingType === 'hmac') {
digest = crypto.createHmac(cryptoAlgName, signingKey)
.update(digstInput)
.digest('base64');
verified = signature === digest;
} else {
var unescapedSignature;
var signatureType = undefined;
signatureType = 'base64';
unescapedSignature = base64urlUnescape(signature);
verified = crypto.createVerify(cryptoAlgName)
.update(digstInput)
.verify(signingKey, unescapedSignature, signatureType);
}
var newJwt = new Jwt(body, false);
newJwt.toString = function () {
return jwtString;
};
newJwt.header = new JwtHeader(header);
if (!verified) {
return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH,jwtString,header,body));
}
return done(null, newJwt);
});
};
Verifier.prototype.withKeyResolver = function withKeyResolver(keyResolver) {
this.keyResolver = keyResolver;
return this;
};
var jwtLib = {
Jwt: Jwt,
JwtBody: JwtBody,
JwtHeader: JwtHeader,
Verifier: Verifier,
base64urlEncode: base64urlEncode,
base64urlUnescape:base64urlUnescape,
verify: function(/*jwtTokenString, [signingKey], [algOverride], [callbck] */){
var args = Array.prototype.slice.call(arguments);
var cb = typeof args[args.length-1] === 'function' ? args.pop() : null;
var verifier = new Verifier();
if(args.length===3){
verifier.setSigningAlgorithm(args[2]);
verifier.setSigningKey(args[1]);
}
if(args.length===2){
verifier.setSigningKey(args[1]);
}
if(args.length===1){
verifier.setSigningAlgorithm('none');
}
return verifier.verify(args[0], cb);
},
createVerifier: function(){
return new Verifier();
},
create: function(claims,secret,alg){
var args = Array.prototype.slice.call(arguments);
var jwt;
if(args.length >= 2){
jwt = new Jwt(claims);
}else if (args.length===1 && typeof claims === 'string'){
jwt = new Jwt({});
secret = claims;
}else{
jwt = new Jwt(claims);
}
if(alg!=='none' && !secret){
throw new Error(properties.errors.SIGNING_KEY_REQUIRED);
}else{
jwt.setSigningAlgorithm(args.length===3 ? alg : 'HS256');
jwt.setSigningKey(secret);
}
jwt.setExpiration((nowEpochSeconds() + (60*60))*1000); // one hour
return jwt;
}
};
module.exports = jwtLib;
{
"errors": {
"PARSE_ERROR": "Jwt cannot be parsed",
"EXPIRED": "Jwt is expired",
"UNSUPPORTED_SIGNING_ALG": "Unsupported signing algorithm",
"SIGNING_KEY_REQUIRED": "Signing key is required",
"SIGNATURE_MISMTACH": "Signature verification failed",
"SIGNATURE_ALGORITHM_MISMTACH": "Unexpected signature algorithm",
"NOT_ACTIVE": "Jwt not active",
"KEY_RESOLVER_ERROR": "Error while resolving signing key for kid \"%s\""
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment