awslabs / cognito-at-edge

Serverless authentication solution to protect your website or Amplify application
Apache License 2.0
168 stars 54 forks source link

IP allowlist functionality #17

Closed trevorrea closed 2 years ago

trevorrea commented 2 years ago

What would you like to be added:

Functionality to configure an allow list to bypass Cognito authentication

Why is this needed:

It's a useful feature :)

See https://github.com/awslabs/cognito-at-edge/pull/16

jeandek commented 2 years ago

Hi Trevor,

Thanks for your interest and for creating this issue/PR. I can see use cases where what you describe would be useful.

However, I am not entirely convinced whether it has its place inside this package. The logic you describe can be performed outside of the Authenticator.handle function by implementing the Lambda@Edge function like so.

const { Authenticator } = require('cognito-at-edge');

// Put any IPs (or CIDR blocks) in this array
const IP_ALLOWLIST = [];

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: 'us-east-1', // user pool region
  userPoolId: 'us-east-1_tyo1a1FHH', // user pool ID
  userPoolAppId: '63gcbm2jmskokurt5ku9fhejc6', // user pool app client ID
  userPoolDomain: 'domain.auth.us-east-1.amazoncognito.com', // user pool domain
});

exports.handler = async (request) => {
  if (IP_ALLOWLIST.length) {
    // IP matching logic goes here
    // return request if match is found
    // otherwise continue
  } 
  return authenticator.handle(request);
}

Still, I will be leaving this issue open for a while to evaluate if there is interest for it. +1s are welcome!

Regards, Jean

trevorrea commented 2 years ago

Hi Jean,

Yep you're right. I told you I wasn't a dev :)

I did try what you said originally and couldn't quite work it out. But based on what you've said I've put together:-

const { Authenticator } = require('cognito-at-edge');

const ipAllowList = [];

const authenticator = new Authenticator({
  cookieExpirationDays: 365,
  disableCookieDomain: false,
  logLevel: 'debug',
  region: 'eu-west-2',
  userPoolAppId: '<placeholder>',
  userPoolAppSecret: '<placeholder>',
  userPoolDomain: '<placeholder>',
  userPoolId: '<placeholder>'
});

exports.handler = async (request) => {
  if (ipAllowList.length) {
    const clientIP = request.Records[0].cf.request.clientIp;
    for (let i = 0; i < ipAllowList.length; i++) {
      if (clientIP == ipAllowList[i]) {
        return request.Records[0].cf.request;
      }
    }      
  }
  return authenticator.handle(request);
}

which works locally when testing with https://www.npmjs.com/package/lambda-local

Not 100% sure why I have to do return authenticator.handle(request); but without that the Lambda executes but doesn't return any result.

I'm off to actually test it in CloudFront now and will be back. Thanks for your help. I'll close this one and maybe open an MR with this added to the README

jeandek commented 2 years ago

You are right, I forgot to the return authenticator.handle(request) in the sample code I gave you. This is needed because the Lambda@Edge response replaces the original request. If the response is empty...