Skip to content

Instantly share code, notes, and snippets.

@ianpward
Last active November 21, 2017 21:35
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ianpward/5207ee4cb6cd0526d7738fc95255497d to your computer and use it in GitHub Desktop.
AWS Cognito Auth for Realm Object Server
"use strict";
var jwt = require('jsonwebtoken');
var request = require('request');
var jwkToPem = require('jwk-to-pem');
/**
* This will be called when the server is started.
*
* It should return the constructor for the authentication provider.
*
* @param {object} deps - The dependencies passed from the running server
* to this implementation.
* @param {function} deps.BaseAuthProvider - the base class to use
* @param {object} deps.problem - a set of exception to throw on failure
* @param {object} deps.models - the models of the admin-Realm
* @returns {function}
*/
module.exports = function(deps) {
return class CognitoAuthProvider extends deps.BaseAuthProvider {
// This is used by the ROS when it loads the available
// authentication providers. This function is required.
static get name() {
return 'custom/cognito';
}
// This is used by ROS when it parses the configuration
// file, to ensure that required default options are
// available. This function is optional.
static get defaultOptions() {
return {
iss: undefined,
}
}
constructor(name, options, requestPromise) {
super(name, options, requestPromise);
this.pems = undefined;
if (!this.options.iss) {
throw new deps.problem.RealmProblem.ServerMisConfiguration({
detail: 'Missing cognito configuration key: iss',
});
}
}
obtainPems() {
if (!this.pems) {
// Download the JWKs and save it as PEM
const httpOptions = {
uri: `${this.options.iss}/.well-known/jwks.json`,
method: 'GET',
json: true,
};
return this.request(httpOptions)
.catch((err) => {
throw new deps.problem.HttpProblem.Unauthorized({
detail: `Unable to retrieve PEMs from Cognito: ${err.toString()}`,
});
})
.then((result) => {
this.pems = {};
var keys = result.keys;
for (var i = 0; i < keys.length; i++) {
// Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
this.pems[key_id] = pem;
}
return this.pems;
});
}
else {
return Promise.resolve(this.pems);
}
}
verifyIdentifier(req) {
// The token submitted by the client
const token = req.body.data;
return this.obtainPems()
.then((pems) => {
// Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {
complete: true
});
if (!decodedJwt) {
throw new deps.problem.HttpProblem.Unauthorized({
detail: 'The token sent by the client was not a valid JWT',
});
}
// Fail if token is not from your User Pool
if (decodedJwt.payload.iss != this.options.iss) {
throw new deps.problem.HttpProblem.Unauthorized({
detail: 'The token sent by the client was issued by the correct ISS',
});
}
// Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'access') {
throw new deps.problem.HttpProblem.Unauthorized({
detail: 'The token sent by the client is not a valid access token',
});
}
// Get the keyId from the token and retrieve corresponding PEM
var keyId = decodedJwt.header.kid;
var pem = pems[keyId];
if (!pem) {
throw new deps.problem.HttpProblem.Unauthorized({
detail: 'The token sent by the client is not a valid access token',
});
}
// Verify the signature of the JWT token to ensure it's really coming from your User Pool
return new Promise((res, rej) => {
jwt.verify(token, pem, {
issuer: this.options.iss
}, (err, payload) => {
if (err) {
rej(err);
}
else {
res(decodedJwt.payload.username);
}
});
});
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment