Miguel Hernández
Miguel Hernández

Reputation: 55

Unable to use custom authorizer in API Gateway

I have a couple of days trying to secure my API Gateway using custom authorizers with the auth0 service. I have my lambda which validates my bearer token, the Lambda does work if I invoke it inside the AWS console and when I create a custom authorizer I can successfully tested with a Bearer token.

When I try to attach the authorizer to my API Gateway methods and test the request with postman and the token provided by auth0 it always returns a 401 status code. I read my logs in CloudWatch and the authorization Lambda it's never triggered whenever I make the HTTP request. I am following this tutorial: https://auth0.com/docs/integrations/aws-api-gateway/custom-authorizers/

And this is my Authorization lambda code:

Handler:

'use strict';

let jwtManager = require("./jwt_manager");

module.exports.authenticate = (event, context, callback) => {

  jwtManager.validate(event, function (error, data) {
    if (error) {
      if (!error) {
        context.fail("Unhandled error");
      }
      context.fail(error);
    }
    else {
      console.log("SUCCEED");
      context.succeed(data);
    }
  });

};

And this is the jwtManager:

"use strict";
require("dotenv").config({ silent: true });

let jwksClient = require("jwks-rsa");
let jwt = require("jsonwebtoken");

module.exports.validate = function(params, callback) {
  var token = validateParams(params);

  var client = jwksClient({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 10,
    jwksUri: process.env.JWKS_URI
  });

  var decodedJwt = jwt.decode(token, { complete: true });
  var kid = decodedJwt.header.kid;

  client.getSigningKey(kid, function(error, data) {
    if (error) {
        console.log(error);
      callback(error);
    } else {
      var signingKey = data.publicKey || data.rsaPublicKey;
      jwt.verify(
        token,
        signingKey,
        { audience: process.env.AUDIENCE, issuer: process.env.ISSUER },
        function(error, decoded) {
            if (error) {
                console.log("ERROR");
                console.log(error);
                callback(error);
            }
            else {
                console.log(decoded);
                var response = {
                    principalId: decoded.sub,
                    policyDocument: getPolicyDocument("Allow", params.methodArn),
                    context: {
                        scope: decoded.scope
                    }
                }
                console.log(response);
                console.log(response.policyDocument);
                callback(null, response);
            }
        }
      );
    }
  });
};

function validateParams(params) {
  var token;

  if (!params.type || params.type !== "TOKEN") {
    throw new Error("Expected 'event.type' parameter to have value TOKEN");
  }

  var tokenString = params.authorizationToken;
  if (!tokenString) {
    throw new Error("Expected 'event.authorizationToken' parameter to be set");
  }

  var match = tokenString.match(/^Bearer (.*)$/);
  if (!match || match.length < 2) {
    throw new Error(
      "Invalid Authorization token - '" +
        tokenString +
        "' does not match 'Bearer .*'"
    );
  }

  return match[1];
}

 function getPolicyDocument(effect, resource) {

    var policyDocument = {};
    policyDocument.Version = '2012-10-17'; // default version
    policyDocument.Statement = [];
    var statementOne = {};
    statementOne.Action = [ 'execute-api:Invoke', 'lambda:Invoke'] ; // default action
    statementOne.Effect = effect;
    statementOne.Resource = resource.split('/')[0] + '/*';
    policyDocument.Statement[0] = statementOne;
    return policyDocument;
}

Thanks in advance!

Upvotes: 2

Views: 4460

Answers (4)

Warren Parad
Warren Parad

Reputation: 4074

It isn't triggered this can be due to one of two reasons:

  • the APIGW authorizer response is cached
  • your configuration expects the token somewhere else.

In the case of the cache, you'll have to wait until it expires or use a different token. For the point of testing, you could remove the cache. In the latter cases, APIGW authorizers automatically reject requests with missing token when the request does not contain the token in the expected location. In those cases your authorizer is not even used.

You can see in this example the authorizer configuration looks at the Authorization header in the identity sources.

enter image description here

In the case you don't specify the authorization header, then the request is automatically rejected.

The other important part of the request is the Authorizer Type. Your code is validating the event.type to be TOKEN. But TOKEN is the legacy authorizer type. The current best practice is to use REQUEST. This exposes the whole request to your authorizer so that you can directly use the request.headers.Authorization header correctly.

It still isn't obvious the best way to handle this so I generally recommend something like this apigw authorizer library and then combining the parsing that library gives you with handling of the request. An example of how to handle the request can be seen here in an apigw authorizer.

Upvotes: 0

jotadepicas
jotadepicas

Reputation: 2493

In my case, the same error (lambda not triggered, authorizer failing) was due to the fact that I didn't deployed the API yet. I may be wrong, but it seems that for testing an Authorizer, your API has to be deployed at least once.

So, I deployed the API, and the authorizer test began to work.

Upvotes: 0

Sagar Jani
Sagar Jani

Reputation: 297

I would like to describe how I resolved this issue.

First thing, the custom authorizer always need bearer token in authorizationToken field but from while invoking API Gateway from Postman or any other client you can send the 'Bearer Token' in authorization header, as this is an industry standard, AWS has supported it.

The trick here is in 'Token Source' while configuring the 'custom authorizer'. I have attached an image here where you can configure that 'Token Source' this field describes that the input to custom authorizer is from 'Authorization Header'. enter image description here This way, you can still send the token in 'Authorzation' header from postman, and API Gateway would copy it from 'Authorization' header and copy it to 'authorizationToken' input field while invoking custom authorizer lambda.

enter image description here Hope it's clear. Let me know if you need more details.

Upvotes: 4

HoongTat Hiew
HoongTat Hiew

Reputation: 36

When you test an API Gateway with a custom authorizer attached but the auth lambda function never triggered, it is likely due to unsuccessful validation in token header name/ token pattern validation.

I am able to reproduce your issue.

The authorizer can only be triggered IF I change the header name from "Authorization" to "AuthorizationToken" in POSTMAN.

check the token header name I made the authorizer works

I think it is likely a new bug in AWS portal as I noticed they have changed the UI to configure API Gateway Authorizers not long ago.

It is very strange a HTTP request has to send bearer token in a header with name "AuthorizationToken". If your AWS plan allows you to access their technical support, you should alert them about this issue.

Upvotes: 2

Related Questions