Coder Social home page Coder Social logo

ghdna / cognito-express Goto Github PK

View Code? Open in Web Editor NEW
213.0 5.0 45.0 637 KB

Authenticates API requests on a Node application by verifying the JWT signature of AccessToken or IDToken generated by Amazon Cognito.

Home Page: https://www.npmjs.com/package/cognito-express

License: MIT License

JavaScript 100.00%
amazon-cognito jwt amazon amazon-web-services cognito-user-pool authentication aws aws-cognito nodejs expressjs

cognito-express's Introduction

Cognito-Express: API Authentication with AWS Congito

NPM

Build Status Package Quality Code Climate Coverage Status dependencies Status Downloads

Synopsis

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

Motivation

Architecture

This module lets you authenticate Node.js API requests by verifying the JWT signature of AccessToken or IDToken - without needing to call Amazon Cognito for each API invocation.

The module can be easily and unobtrusively integrated into any application or framework that supports Connect-style middleware, including Express.

This module essentially bundles steps 1-7 listed on the official AWS documentation on Using ID Tokens and Access Tokens in your Web APIs

  1. Download and store the JSON Web Token (JWT) set for your user pool.
  2. Decode the token string into JWT format.
  3. Check the iss claim. It should match your user pool.
  4. Check the tokenUse claim. It should match your set preference for access or id token types
  5. Get the kid from the JWT token header and retrieve the corresponding JSON Web Key that was stored in step 1.
  6. Verify the signature of the decoded JWT token.
  7. Check the exp claim and make sure the token is not expired.

You can now trust the claims inside the token and use it as it fits your requirements.

Prerequisites

After successful authentication of a user, Amazon Cognito issues three tokens to the client:

  • ID token
  • Access token
  • Refresh token

(Note: The login mechanism is not covered by this module and you'll have to build that separately)

Save these tokens within the client app (preferably as cookies). When any API is invoked from client, pass in the AccessToken or IDToken to the server.

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.

Configuration

//Initializing CognitoExpress constructor
const cognitoExpress = new CognitoExpress({
	region: "us-east-1",
	cognitoUserPoolId: "us-east-1_dXlFef73t",
	tokenUse: "access", //Possible Values: access | id
	tokenExpiration: 3600000 //Up to default expiration of 1 hour (3600000 ms)
});

Usage

cognitoExpress.validate(accessTokenFromClient, function(err, response) {
	if (err) {
		/*
			//API is not authenticated, do something with the error.
		    //Perhaps redirect user back to the login page
			
			//ERROR TYPES:
			
			//If accessTokenFromClient is null or undefined
			err = {
			    "name": "TokenNotFound",
			    "message": "access token not found"
			}
			
			//If tokenuse doesn't match accessTokenFromClient
			{
			    "name": "InvalidTokenUse",
			    "message": "Not an id token"
			}

			//If token expired
			err = {
			    "name": "TokenExpiredError",
			    "message": "jwt expired",
			    "expiredAt": "2017-07-05T16:41:59.000Z"
			}

			//If token's user pool doesn't match the one defined in constructor
			{
			    "name": "InvalidUserPool",
			    "message": "access token is not from the defined user pool"
			}

		*/
	} else {
		//Else API has been authenticated. Proceed.
		res.locals.user = response; //Optional - if you want to capture user information
		next();
	}
});

Also supports async/await pattern

(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
//app.js
"use strict";

const express = require("express"),
	CognitoExpress = require("cognito-express"),
	port = process.env.PORT || 8000;

const app = express(),
	authenticatedRoute = express.Router(); //I prefer creating a separate Router for authenticated requests

app.use("/api", authenticatedRoute);

//Initializing CognitoExpress constructor
const cognitoExpress = new CognitoExpress({
	region: "us-east-1",
	cognitoUserPoolId: "us-east-1_dXlFef73t",
	tokenUse: "access", //Possible Values: access | id
	tokenExpiration: 3600000 //Up to default expiration of 1 hour (3600000 ms)
});

//Our middleware that authenticates all APIs under our 'authenticatedRoute' Router
authenticatedRoute.use(function(req, res, next) {
	
	//I'm passing in the access token in header under key accessToken
	let accessTokenFromClient = req.headers.accesstoken;

	//Fail if token not present in header. 
	if (!accessTokenFromClient) return res.status(401).send("Access Token missing from header");

	cognitoExpress.validate(accessTokenFromClient, function(err, response) {
		
		//If API is not authenticated, Return 401 with error message. 
		if (err) return res.status(401).send(err);
		
		//Else API has been authenticated. Proceed.
		res.locals.user = response;
		next();
	});
});


//Define your routes that need authentication check
authenticatedRoute.get("/myfirstapi", function(req, res, next) {
	res.send(`Hi ${res.locals.user.username}, your API call is authenticated!`);
});

app.listen(port, function() {
	console.log(`Live on port: ${port}!`);
});
client.js - angular example
//client.js - angular example

"use strict";

//I stored my access token value returned from Cognito in a cookie called ClientAccessToken

app.controller("MyFirstAPI", function($scope, $http, $cookies) {
	$http({
		method: "GET",
		url: "/api/myfirstapi",
		headers: {
			accesstoken: $cookies.get("ClientAccessToken") 
            }
		}
	}).then(
		function success(response) {
			//Authenticated. Do something with the response. 
		},
		function error(err) {
			console.error(err);
		}
	);
});

Contributors

Gary Arora

License

MIT

cognito-express's People

Contributors

dependabot[bot] avatar falconmaster94 avatar ghdna avatar j-5-s avatar jcardus avatar panuhorsmalahti avatar rivertam avatar ss-vijayvavdiya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

cognito-express's Issues

JWKs rotation

seems module is missing option to download new JWKs if kid is missing in existing 'cache'.
it can cause an issue when Cognito is rotating signing keys.

tokenExpiration expects seconds, not microseconds

The README shows an example tokenExpiration value of 3600000, with a comment saying "1 hour."

It's sending that value to https://github.com/auth0/node-jsonwebtoken as the "maxAge" parameter. According to that module's docs (and to my own test of this module), if you pass an integer it will interpret the value as seconds.

So setting tokenExpiration to 30 will set a 30 minute maximum on the auth token.

Getting User's Status From Cognito

After reading this issue: #4

I see alot of user attributes are shown except the user's status,. This is the user's status I am talking about from Cognito
image

How would one retrieve this?

Runtime.UnhandledPromiseRejection

    "errorType": "Runtime.UnhandledPromiseRejection",
    "errorMessage": "TypeError: Unable to generate certificate due to \nRequestError: Error: Client network socket disconnected before secure TLS connection was established",
    "reason": {
        "errorType": "TypeError",
        "errorMessage": "Unable to generate certificate due to \nRequestError: Error: Client network socket disconnected before secure TLS connection was established",
        "stack": [
            "TypeError: Unable to generate certificate due to ",
            "RequestError: Error: Client network socket disconnected before secure TLS connection was established",
            "    at /var/task/node_modules/cognito-express/lib/strategy.js:42:23",
            "    at bound (domain.js:427:14)",
            "    at runBound (domain.js:440:12)",
            "    at tryCatcher (/var/task/node_modules/bluebird/js/release/util.js:16:23)",
            "    at Promise._settlePromiseFromHandler (/var/task/node_modules/bluebird/js/release/promise.js:547:31)",
            "    at Promise._settlePromise (/var/task/node_modules/bluebird/js/release/promise.js:604:18)",
            "    at Promise._settlePromise0 (/var/task/node_modules/bluebird/js/release/promise.js:649:10)",
            "    at Promise._settlePromises (/var/task/node_modules/bluebird/js/release/promise.js:725:18)",
            "    at _drainQueueStep (/var/task/node_modules/bluebird/js/release/async.js:93:12)",
            "    at _drainQueue (/var/task/node_modules/bluebird/js/release/async.js:86:9)",
            "    at Async._drainQueues (/var/task/node_modules/bluebird/js/release/async.js:102:5)",
            "    at Immediate.Async.drainQueues [as _onImmediate] (/var/task/node_modules/bluebird/js/release/async.js:15:14)",
            "    at processImmediate (internal/timers.js:456:21)",
            "    at process.topLevelDomainCallback (domain.js:138:15)",
            "    at process.callbackTrampoline (internal/async_hooks.js:118:14)"
        ]
    },
    "promise": {
        "isFulfilled": false,
        "isRejected": true,
        "rejectionReason": {
            "errorType": "TypeError",
            "errorMessage": "Unable to generate certificate due to \nRequestError: Error: Client network socket disconnected before secure TLS connection was established",
            "stack": [
                "TypeError: Unable to generate certificate due to ",
                "RequestError: Error: Client network socket disconnected before secure TLS connection was established",
                "    at /var/task/node_modules/cognito-express/lib/strategy.js:42:23",
                "    at bound (domain.js:427:14)",
                "    at runBound (domain.js:440:12)",
                "    at tryCatcher (/var/task/node_modules/bluebird/js/release/util.js:16:23)",
                "    at Promise._settlePromiseFromHandler (/var/task/node_modules/bluebird/js/release/promise.js:547:31)",
                "    at Promise._settlePromise (/var/task/node_modules/bluebird/js/release/promise.js:604:18)",
                "    at Promise._settlePromise0 (/var/task/node_modules/bluebird/js/release/promise.js:649:10)",
                "    at Promise._settlePromises (/var/task/node_modules/bluebird/js/release/promise.js:725:18)",
                "    at _drainQueueStep (/var/task/node_modules/bluebird/js/release/async.js:93:12)",
                "    at _drainQueue (/var/task/node_modules/bluebird/js/release/async.js:86:9)",
                "    at Async._drainQueues (/var/task/node_modules/bluebird/js/release/async.js:102:5)",
                "    at Immediate.Async.drainQueues [as _onImmediate] (/var/task/node_modules/bluebird/js/release/async.js:15:14)",
                "    at processImmediate (internal/timers.js:456:21)",
                "    at process.topLevelDomainCallback (domain.js:138:15)",
                "    at process.callbackTrampoline (internal/async_hooks.js:118:14)"
            ]
        }
    },
    "stack": [
        "Runtime.UnhandledPromiseRejection: TypeError: Unable to generate certificate due to ",
        "RequestError: Error: Client network socket disconnected before secure TLS connection was established",
        "    at process.<anonymous> (/var/runtime/index.js:35:15)",
        "    at process.emit (events.js:327:22)",
        "    at process.EventEmitter.emit (domain.js:483:12)",
        "    at eventToObjectGenerator.promiseCreated (/var/task/node_modules/bluebird/js/release/debuggability.js:258:33)",
        "    at activeFireEvent (/var/task/node_modules/bluebird/js/release/debuggability.js:301:44)",
        "    at fireRejectionEvent (/var/task/node_modules/bluebird/js/release/debuggability.js:703:14)",
        "    at Promise._notifyUnhandledRejection (/var/task/node_modules/bluebird/js/release/debuggability.js:90:9)",
        "    at Timeout.unhandledRejectionCheck [as _onTimeout] (/var/task/node_modules/bluebird/js/release/debuggability.js:39:25)",
        "    at listOnTimeout (internal/timers.js:549:17)",
        "    at processTimers (internal/timers.js:492:7)"
    ]
}


enviroment:

provider:
name: aws
runtime: nodejs12.x
lambda

CVE-2023-45857 - axios

An issue discovered in Axios 1.5.1 ( or older) inadvertently reveals the confidential XSRF-TOKEN stored in cookies by including it in the HTTP header X-XSRF-TOKEN for every request made to any host allowing attackers to view sensitive information.
See https://security.snyk.io/vuln/SNYK-JS-AXIOS-6032459

The issue was resolved on Axios 1.6.0.

Please update Axios package to 1.6.0 or newer.

Cognito Token JsonWebTokenError: invalid signature

Hello,
We have Cognito and ExpressJS app.
Our application has only the use case where we need to validate the tokens (no generation/login/registration - this is done on React side via Authorization Code Flow with PKCE).
I have installed the dependency, configured the object

const cognitoExpress = new CognitoExpress({
	region: "us-east-1",
	cognitoUserPoolId: "us-east-1_123123",
	tokenUse: "id", //Possible Values: access | id
	tokenExpiration: 3600000 //Up to default expiration of 1 hour (3600000 ms)
});

and in the endpoint that i need authn/z i have the following

try {
    const response = await cognitoExpress.validate(accessTokenFromClient);
    console.log(response);
    } catch(e) {
        console.log(`EXCEPTION`);
        console.log(e);
    }

It seems that an exception is thrown and the error that i am getting is JsonWebTokenError: invalid signature.

Any idea what i am missing? I would assume the JWKS keys used for validations are fetched from the library itself?

Contributions for cognito-express

Hi!

In the process of ensuring the functionality of my authentication middleware, I have written two pieces of code that you may find interesting for your own usage in cognito-express's development:

  1. An e2e test suite that tests the basic use-cases for token validation
  2. A quick & dirty utility script used to force reset the password of a user created via the Cognito User Pool interface

For context, my authenticationMiddleware is implemented as follows:

function authenticationMiddleware(poolOptions = null) {
  // Initializing CognitoExpress constructor
  const cognitoExpress = new CognitoExpress(
    typeof poolOptions === 'object' && poolOptions !== null
      ? poolOptions
      : {
        region: process.env.COGNITO_REGION,
        cognitoUserPoolId: process.env.COGNITO_USERPOOL_ID,
        tokenUse: 'access', // Possible Values: access | id
        tokenExpiration: parseInt(process.env.COGNITO_TOKEN_EXPIRATION, 10),
      }
  );

  cognitoExpress.validate = util.promisify(cognitoExpress.validate);

  return async function innerAuthenticationMiddleware(req, res, next) {
    // I'm passing in the access token in header under key accessToken
    const authJwtToken = req.headers.Authorization;

    // Fail if token not present in header.
    if (!authJwtToken) {
      return res.status(401).send('Access Token missing from header');
    }

    try {
      const authResponse = await cognitoExpress.validate(authJwtToken);
      // API has been authenticated. Proceed.
      req.locals.user = authResponse;
      next();
    } catch (err) {
      // If API is not authenticated, Return 401 with error message.
      return res.status(401).send(err);
    }
  };
}

Hope that these can be of use!

Have a great day ๐Ÿš€

Cheers,

Phil

Invalid JWT token results in error object in callback as string rather than object

Not sure if this extends to other error cases, but when a malformed Bearer token is sent to .validate(token, callback), the error object passed to the callback is a string value of Not a valid JWT token

expected behaviour:

  • Invalid JWT token values result in an error object with keys name and message like other error objects

Handeling refresh tokens

First af all: great job for all of the work you have been doing on this plugin. It works like a charm!

I was wondering if anyone has yet implemented a flow for handling expired access / id tokens and using the refresh token to retrieve a new jwt? I am thinking about building this functionality and I wonder what would be the best approach. I currently have a separate unauthenticated endpoint to which a refresh token can be send (along with the username since I think its necessary for the exchange). The cognito-express module could also catch the expired like this:

cognitoExpress.validate(authToken, function (err, response) { if(err.name === 'TokenExpiredError') {} if (err) { return res.status(401).send(err); }
Who implemented this part and can give me some leads on how to implement this part securely?

'aud' claim check

As per AWS Cognito documentation:

"The audience (aud) claim should match the app client ID created in the Amazon Cognito user pool."

The module doesn't seem to verify the 'aud' claim. Am I missing something or is this a bug?

is the <tokenExpiration> really mandatory/needed?

Developer sets the access/refresh tokens expiration policy in the Cognito Admin panel.
Then client gets token that has <issued_at> and <expires_at>

Why does Server (cognito-express) need to bother with the aforementioned setting?

Get user attributes

First of all very nice plugin, simple and clear, exactly what I've looked for.
The question is there an option to get user attributes (like email for instance)?
When validating token, got response as user information, but I can't find there attributes.

Local read of JWT tokens

Hi,

Would it be possible to provide ability to load JWT tokens from file system (for example) and pass them into the lib rather than making network call? The reason is our corporate proxy blocks the request from NodeJS express app, which is the main reason I'm thinking of switching from server side to client side authentication, which means I need to use the tokens.

In this scenario I would probably package the tokens with the application.

p.s. Do you know if the tokens change once the user pool is created? - aws documentation isn't clear on that. For the user pools I've created there seem to be always 2 tokens.

Thanks,
Paul

Needle fails to download certificate on first request on AWS Lambda

Hey! I'm getting an error on AWS Lambda that I don't see on other platforms.

It looks like needle is failing to download the well-known certs on the first request, but I believe this only happens on lambda boot, and it succeeds on the second request:

TypeError: Unable to generate certificate
    at needle.get (/var/task/node_modules/cognito-express/lib/strategy.js:60:23)
    at done (/var/task/node_modules/needle/lib/needle.js:437:14)
    at ClientRequest.had_error (/var/task/node_modules/needle/lib/needle.js:447:5)
    at emitOne (events.js:96:13)
    at ClientRequest.emit (events.js:188:7)
    at TLSSocket.socketCloseListener (_http_client.js:285:9)
    at emitOne (events.js:101:20)
    at TLSSocket.emit (events.js:188:7)
    at _handle.close (net.js:497:12)
    at TCP.done [as _onclose] (_tls_wrap.js:332:7)

This error goes away when I replace needle with request.

cors issue

Issued error msg on the client:
Access to XMLHttpRequest at 'http://localhost:3000/test' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Headers set on the server:
app.use("/api", authenticatedRoute); app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization"); next(); });

Any help setting the correct headers would be much appreciated!

Excessive Logging

Version

2.0.18

Description

The validate() function appears to be doing some sort of excessive logging.

Steps to reproduce

Add this middleware or a similar middleware

export const cognitoAuth = (req, res, next) => {
  const auth = req.headers.authorization
  const authorization = auth && auth.length > 7 ? auth.substr(7, auth.length) : ''
   console.log('before authorization')
  cognitoExpress.validate(authorization, (err, resp) => {
    if (err) {
      res.status(401)
      return res.send(new Error('Unauthorized'))
    } else {
      console.log('After Authorization')
      next()
    }
  })
}

You will receive in your console

before authorization

followed by (I replaced values with 000 and '...')

{ header:
   { kid: '...',
     alg: 'RS256' },
  payload:
   { sub: '...',
     event_id: '...',
     token_use: 'access',
     scope: 'openid profile email',
     auth_time: 000,
     iss:
      '...',
     exp: 000,
     iat:000.,
     version: 2,
     jti: '...',
     client_id: '...',
     username: '...' },
  signature:
   '....' { decode: [Function],
  verify: [Function],
  sign: [Function],
  JsonWebTokenError: [Function: JsonWebTokenError],
  NotBeforeError: [Function: NotBeforeError],
  TokenExpiredError: [Function: TokenExpiredError] }

followed by

after authorization

Correct me if I'm wrong of course, but this is what I was receiving with 2.0.18

Refactor `Strategy.init()`into a smarter implementation

This issue stems from the statements first made in #

Furthermore, according to this AWS Forum post (dated Nov 2016):

Cognito UserPool currently does not rotate keys but this behavior can be changed in future. For example, we may periodically rotate keys or allow a developer to replace the keys. We recommend that you cache each key in jwks uri against kid. Now when you process Id or Access token in your APIs, you should check the kid in JWT header and retrieve the key from cache. When your API sees token with different kid, you should query jwks uri again to check if keys have been changed and update your cache accordingly with new keys.

How to make the https://cognito-idp request from Proxy from with-in the app?

Hi,
The request using the URL: https://cognito-idp.${REGION}.amazonaws.com/${POOLID}/.well-known/jwks.json is being made from strategy.js while running the app initially, but getting the following error:
Unhandled rejection TypeError: Unable to generate certificate due to
RequestError: Error: connect ECONNREFUSED 54.209.125.172:443

I'm able to hit the same from the browser and see the JSON response. I'm assuming that the issue could be because of company's proxy, but not sure how to make this work from with-in the app. Any insights, please?

How to deal with constructor background error

Hello,

From what I understand, when the constructor is called, there is an http request launched in the background to download the jwks.json. If for some reason that HTTP request fails, an error is then thrown.

Is there any way to handle that error during the constructor instantiation? I'd like to avoid continuing in the code if this fails.

Thanks,
Joel

tokenExpiration question

Since the accessToken contains the token expiration timestamp in it, what is the purpose of defining the token expiration on the config?

Does CognitoExpress return an invalid response if the Token is in fact expired? and is based on on the auth_time + tokenExpiration values or the accessToken "exp" value?

thank you in advance for your response.

Bogus JsonWebTokenError on maxAge value

After getting an AWS ID Token and passing it to my API using the cognito-express middleware, I get the following error:

{
    "name":"JsonWebTokenError",
    "message":"\"maxAge\" should be a number of seconds or string representing a timespan eg: \"1d\", \"20h\", 60"
}

This is unusable.

Curl Method Option needed to get around issues running cognito-express in Ubuntu on windows + Windows Powershell

Hi, I implemented cognito-express in my mac osx environment. Then the devs on my team pulled in the changes to ubuntu on windows and windows PowerShell and kept getting "could not get certificate" errors from the request-promise call in the init method.

Their laptops have more security features than my mac and for some reason, this library was not working for us on all operating systems/setups. I modified the source code to use shelljs + a curl command to get the certificates instead, and now it works on all of our operating systems.

I just wanted to mention this in case anyone else runs into this issue. Also, I think it could be useful to add this logic to the library as an optional strategy in case others also have trouble using this library across multiple systems.

'use strict'

const jwkToPem = require('jwk-to-pem'),
  jwt = require('jsonwebtoken'),
  shelljs = require('shelljs')

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((callback) => {})
    }
  }

  init(callback) {
    return new Promise((resolve, reject) => {
      const child = shelljs.exec(
        `curl ${`${this.iss}/.well-known/jwks.json`}`,
        {
          async: true,
          silent: true,
        }
      )

      child.stdout.on('data', (data) => {
        try {
          const response = JSON.parse(data)

          if (response) {
            this.pems = {}
            let keys = response['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
            }

            callback(true)
            resolve(true)
          } else {
            callback(false)
            reject(false)
          }
        } catch (error) {
          callback(false)
          reject(false)
        }

        child.kill()
      })
    })
  }

  validate(token, callback) {
    const p = this.promise
      .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,
          }
          if (callback) {
            jwtVerify(params, callback)
          } else {
            return new Promise((resolve, reject) => {
              jwtVerify(params, (err, result) => {
                if (err) {
                  reject(err)
                } else {
                  resolve(result)
                }
              })
            })
          }
        } catch (err) {
          if (!callback) throw err

          callback(err.message, null)
        }
      })
      .catch((e) => callback(e?.message || e, null))

    if (!callback) {
      return p
    }
  }
}

function configurationIsCorrect(config) {
  let configurationPassed = false
  switch (true) {
    case !config.region:
      throw new TypeError('AWS Region not specified in constructor')
      break
    case !config.cognitoUserPoolId:
      throw new TypeError(
        'Cognito User Pool ID is not specified in constructor'
      )
      break
    case !config.tokenUse:
      throw new TypeError(
        "Token use not specified in constructor. Possible values 'access' | 'id'"
      )
      break
    case !(config.tokenUse == 'access' || config.tokenUse == 'id'):
      throw new TypeError(
        "Token use values not accurate in the constructor. Possible values 'access' | 'id'"
      )
      break
    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)
    }
  )
}

module.exports = CognitoExpress

Getting socket hang up error randomly when deployed on lambda

This issue occurs very randomly and is automatically resolved after few mins. I have attached the cloudwatch logs below for reference.

Unable to generate certificate due to
RequestError: Error: socket hang up
at /var/task/node_modules/cognito-express/lib/strategy.js:41:23
at tryCatcher (/var/task/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/var/task/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise (/var/task/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromise0 (/var/task/node_modules/bluebird/js/release/promise.js:614:10)
at Promise._settlePromises (/var/task/node_modules/bluebird/js/release/promise.js:690:18)
at _drainQueueStep (/var/task/node_modules/bluebird/js/release/async.js:138:12)
at _drainQueue (/var/task/node_modules/bluebird/js/release/async.js:131:9)
at Async._drainQueues (/var/task/node_modules/bluebird/js/release/async.js:147:5)
at Immediate.Async.drainQueues (/var/task/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:794:20)
at tryOnImmediate (timers.js:752:5)
at processImmediate [as _immediateCallback] (timers.js:729:5)

post test coverage reports fail

It looks like this portion of the unit test command fails:

..."cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"

Error message:

/Users/nnn/cognito-express/node_modules/coveralls/bin/coveralls.js:18
throw err;
^
Bad response: 422 {"message":"Couldn't find a repository matching this job.","error":true}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] test: istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] test script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

Would be awesome to use promises for this

Had to write a wrapper so that i can use this library with async/await, would be nice to have this use promises internally

export default token =>
  new Promise((resolve, reject) => {
    cognitoExpress.validate(token, (err, response) => {
      if (err) {
        return reject(err)
      }
      resolve(response)
    })
  })

`CognitoExpress.validate()`'s `callback` parameter is optional, but is blindly invoked for error conditions

This means that the promisified code path in CognitoExpress.validate() is fundamentally broken for failure scenarios.

Here are the offending lines:

if (!decodedJwt) return callback(`Not a valid JWT token`, null);
if (decodedJwt.payload.iss !== this.iss)
return callback(`token is not from your User Pool`, null);
if (decodedJwt.payload.token_use !== this.tokenUse)
return callback(`Not an ${this.tokenUse} token`, null);
let kid = decodedJwt.header.kid;
let pem = this.pems[kid];
if (!pem) return callback(`Invalid ${this.tokenUse} token`, null);

Meanwhile, just a little further down callback is properly checked in two locations:

if (callback) {
jwtVerify(params, callback);
} else {
return new Promise((resolve, reject) => {
jwtVerify(params, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
});
if (!callback) {
return p;
}

Refactor `Strategy.init()` into an intelligent caching mechanism

This issue stems from the comments started in #20, where it was brought up that it is unclear whether clients should be responsible for calling init(). The Strategy constructor calls init() and the results are cached from that point forward. Given the following fact, from this AWS Forum post (dated Nov 2016), that is currently sufficient.

Cognito UserPool currently does not rotate keys but this behavior can be changed in future. For example, we may periodically rotate keys or allow a developer to replace the keys. We recommend that you cache each key in jwks uri against kid. Now when you process Id or Access token in your APIs, you should check the kid in JWT header and retrieve the key from cache. When your API sees token with different kid, you should query jwks uri again to check if keys have been changed and update your cache accordingly with new keys.

So to be completely safe and future-proof with the way this project is structured today, clients should explicitly call init() periodically. Or before every call to validate(), which is how most of the unit tests are written. Their execution times infer the network request for fetching the JWKs incurs a penalty on the order of 100+ milliseconds, even though that penalty is likely completely unnecessary.

init() could be rewritten, or perhaps effectively, merged into validate(), and take the advice from the forum post above with JWK caching and fetching on demand only when warranted.

This sounds like a breaking change, but could be implemented in such a way that init() could be left as a deprecated stub that simply resolves immediately.

Add TypeScript Support to cognito-express

src/middleware/index.ts:1:28 - error TS7016: Could not find a declaration file for module 'cognito-express'. '/Users/naishe/app/node_modules/cognito-express/lib/index.js' implicitly has an 'any' type.
  Try `npm install @types/cognito-express` if it exists or add a new declaration (.d.ts) file containing `declare module 'cognito-express';`

Please add support for TypeScript.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.