Skip to content

Commit

Permalink
enabled async/await functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ghdna committed Jan 18, 2022
1 parent 40925f7 commit 52980e3
Show file tree
Hide file tree
Showing 4 changed files with 1,648 additions and 2,052 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
cognito-express authenticates API requests on a Node.js application (either running on a server or in an AWS Lambda function) by verifying the JWT signature of `AccessToken` or `IDToken` generated by Amazon Cognito.


![Architecture](https://i.ibb.co/RyrzfXD/Screen-Shot-2022-01-17-at-19-22-18.png)

## Motivation


Expand Down Expand Up @@ -51,7 +53,7 @@ When any API is invoked from client, pass in the `AccessToken` or `IDToken` to t

It's completely up to you how you pass in the `AccessToken` or `IDToken`. Here are two options:
1. By adding them explicitly in Request Headers
2. Just save the tokens as cookies. This way they get attached to request headers whenever APIs are invoked. (recommended)
2. Just save the tokens as cookies. This way they get attached to request headers whenever APIs are invoked.

## Configuration
```javascript
Expand Down Expand Up @@ -106,8 +108,26 @@ cognitoExpress.validate(accessTokenFromClient, function(err, response) {
}
});

```

Also supports async/await pattern

```javascript
(async function main() {
try {
const response = await cognitoExpress.validate(accessTokenFromClient);
console.log(response);
//User is authenticated, proceed with rest of your business logic.

} catch (e) {
console.error(e);
//User is not authenticated, do something with the error.
//Perhaps redirect user back to the login page
}
})();
```


## Full Example
###### app.js - server
```javascript
Expand Down
239 changes: 109 additions & 130 deletions lib/strategy.js
Original file line number Diff line number Diff line change
@@ -1,142 +1,121 @@
"use strict";

const jwkToPem = require("jwk-to-pem"),
request = require("request-promise"),
axios = require("axios"),
jwt = require("jsonwebtoken");
axios = require("axios"),
jwt = require("jsonwebtoken");

class CognitoExpress {
constructor(config) {
if (!config)
throw new TypeError(
"Options not found. Please refer to README for usage example at https://github.com/ghdna/cognito-express"
);

if (configurationIsCorrect(config)) {
this.userPoolId = config.cognitoUserPoolId;
this.tokenUse = config.tokenUse;
this.tokenExpiration = config.tokenExpiration || 3600000;
this.iss = `https://cognito-idp.${config.region}.amazonaws.com/${this
.userPoolId}`;
// this.promise = this.init();
this.hasFinishedProcessing = this.init()
this.pems = {}

}
}

init() {
return new Promise(async (resolve, reject) => {
try {
const axiosResponse = await axios(`${this.iss}/.well-known/jwks.json`)
if (axiosResponse.data.keys) {

const keys = axiosResponse.data.keys;
for (let i = 0; i < keys.length; i++) {
let key_id = keys[i].kid;

let modulus = keys[i].n;
let exponent = keys[i].e;
let key_type = keys[i].kty;
let jwk = {
kty: key_type,
n: modulus,
e: exponent
};
let pem = jwkToPem(jwk);
this.pems[key_id] = pem;
}
resolve()
}
} catch (err) {
reject("Unable to generate certificate due to \n" + err)
console.log(err)
}
});

constructor(config) {
if (!config)
throw new TypeError(
"Options not found. Please refer to README for usage example at https://github.com/ghdna/cognito-express"
);

if (configurationIsCorrect(config)) {
this.userPoolId = config.cognitoUserPoolId;
this.tokenUse = config.tokenUse;
this.tokenExpiration = config.tokenExpiration || 3600000;
this.iss = `https://cognito-idp.${config.region}.amazonaws.com/${this.userPoolId}`;
this.hasFinishedProcessing = this.init();
this.pems = {};
}

validate(token, callback) {

this.hasFinishedProcessing.then(() => {
let decodedJwt = jwt.decode(token, {
complete: true
});
try {
if (!decodedJwt) throw new TypeError('Not a valid JWT token');

if (decodedJwt.payload.iss !== this.iss)
throw new TypeError('token is not from your User Pool');

if (decodedJwt.payload.token_use !== this.tokenUse)
throw new TypeError(`Not an ${this.tokenUse} token`);

let kid = decodedJwt.header.kid;
let pem = this.pems[kid];

if (!pem) throw new TypeError(`Invalid ${this.tokenUse} token`);

let params = {
token: token,
pem: pem,
iss: this.iss,
maxAge: this.tokenExpiration
};

jwtVerify(params, (err, result) => {
if (err) {
callback(err.name, null)
} else {
callback(null, result)
}
});

} catch (err) {

}
}

init() {
return new Promise(async (resolve, reject) => {
try {
const response = await axios(`${this.iss}/.well-known/jwks.json`);
if (response.data.keys) {
const keys = response.data.keys;
for (let i = 0; i < keys.length; i++) {
let key_id = keys[i].kid;

let modulus = keys[i].n;
let exponent = keys[i].e;
let key_type = keys[i].kty;
let jwk = {
kty: key_type,
n: modulus,
e: exponent,
};
let pem = jwkToPem(jwk);
this.pems[key_id] = pem;
}
resolve();
}
} catch (err) {
console.error(err);
reject("Unable to generate certificate due to \n" + err);
}
});
}

async validate(token, callback) {
await this.hasFinishedProcessing;
return new Promise(async (resolve, reject) => {
let decodedJwt = jwt.decode(token, {
complete: true,
});
try {
if (!decodedJwt) throw new TypeError("Not a valid JWT token");

if (decodedJwt.payload.iss !== this.iss)
throw new TypeError("token is not from your User Pool");

if (decodedJwt.payload.token_use !== this.tokenUse)
throw new TypeError(`Not an ${this.tokenUse} token`);

let kid = decodedJwt.header.kid;
let pem = this.pems[kid];

if (!pem) throw new TypeError(`Invalid ${this.tokenUse} token`);

const result = jwt.verify(token, pem, {
issuer: this.iss,
maxAge: this.tokenExpiration,
});
}
if (callback) {
callback(null, result);
} else {
resolve(result);
}
} catch (error) {
console.error(error);
if (callback) {
callback(error, null);
} else {
reject(error);
}
}
});
}
}

function configurationIsCorrect(config) {
let configurationPassed = false;
switch (true) {
case !config.region:
throw new TypeError("AWS Region not specified in constructor");

case !config.cognitoUserPoolId:
throw new TypeError(
"Cognito User Pool ID is not specified in constructor"
);

case !config.tokenUse:
throw new TypeError(
"Token use not specified in constructor. Possible values 'access' | 'id'"
);

case !(config.tokenUse == "access" || config.tokenUse == "id"):
throw new TypeError(
"Token use values not accurate in the constructor. Possible values 'access' | 'id'"
);

default:
configurationPassed = true;
}
return configurationPassed;
}

function jwtVerify(params, callback) {
jwt.verify(
params.token,
params.pem, {
issuer: params.iss,
maxAge: params.maxAge
},
function(err, payload) {
if (err) return callback(err, null);
return callback(null, payload);
}
);
let configurationPassed = false;
switch (true) {
case !config.region:
throw new TypeError("AWS Region not specified in constructor");

case !config.cognitoUserPoolId:
throw new TypeError(
"Cognito User Pool ID is not specified in constructor"
);

case !config.tokenUse:
throw new TypeError(
"Token use not specified in constructor. Possible values 'access' | 'id'"
);

case !(config.tokenUse == "access" || config.tokenUse == "id"):
throw new TypeError(
"Token use values not accurate in the constructor. Possible values 'access' | 'id'"
);

default:
configurationPassed = true;
}
return configurationPassed;
}

module.exports = CognitoExpress;
module.exports = CognitoExpress;
Loading

0 comments on commit 52980e3

Please sign in to comment.