Repository: serverless/serverless (JavaScript)
Date: 2017-10-10 18:50:14+00:00
Task type: Feature

Issue:
Change Authorizer type to "REQUEST" (instead of "TOKEN")
# This is a Feature Proposal
## Description
Change Authorizer type to "REQUEST" (instead of "TOKEN") so that the Authorizer function receives all of the parameters passed to the main function (through lamba-proxy).

As-is, it is not possible to use any parameters in the Authorizer function (other than a single authorization header) which greatly restricts what is possible in the Authorizer.

See https://forum.serverless.com/t/set-custom-authorizer-type-to-request-not-token/2760/3

## Additional Data

* ***Serverless Framework Version you're using***:
* ***Operating System***:
* ***Stack Trace***:
* ***Provider Error messages***:



Issue discussion:
Thank you for the suggestion @craigdrayton 😁 That's a nice idea 👍
Only one concern is that it would be a breaking change. We need to confirm that.
To avoid the breaking change, could leave TOKEN as the default with REQUEST available as an option. Cheers :-)
Thank @craigdrayton for getting back 🙌

How do the parameters look like with REQUEST?
If only adding some json items based on TOKEN, it would not be the breaking change. 
AWS implementation details here:
https://aws.amazon.com/about-aws/whats-new/2017/09/amazon-api-gateway-now-supports-enhanced-request-authorizers/

http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html

It may be as simple as setting 'type' to 'REQUEST':
http://docs.aws.amazon.com/apigateway/api-reference/resource/authorizer/#type

Thanks @craigdrayton for providing the official information 👍

Here is Token type
```
{
    "type":"TOKEN",
    "authorizationToken":"<caller-supplied-token>",
    "methodArn":"arn:aws:execute-api:<regionId>:<accountId>:<apiId>/<stage>/<method>/<resourcePath>"
}
```

Here is REQUEST type
```
{
    "type": "REQUEST",
    "methodArn": "arn:aws:execute-api:us-east-1:123456789012:s4x3opwd6i/test/GET/request",
    "resource": "/request",
    "path": "/request",
    "httpMethod": "GET",
    "headers": {
        "X-AMZ-Date": "20170718T062915Z",
        "Accept": "*/*",
        "HeaderAuth1": "headerValue1",
        "CloudFront-Viewer-Country": "US",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Is-Mobile-Viewer": "false",
        "User-Agent": "...",
        "X-Forwarded-Proto": "https",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "Host": "....execute-api.us-east-1.amazonaws.com",
        "Accept-Encoding": "gzip, deflate",
        "X-Forwarded-Port": "443",
        "X-Amzn-Trace-Id": "...",
        "Via": "...cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "...",
        "X-Forwarded-For": "..., ...",
        "Postman-Token": "...",
        "cache-control": "no-cache",
        "CloudFront-Is-Desktop-Viewer": "true",
        "Content-Type": "application/x-www-form-urlencoded"
    },
    "queryStringParameters": {
        "QueryString1": "queryValue1"
    },
    "pathParameters": {},
    "stageVariables": {
        "StageVar1": "stageValue1"
    },
    "requestContext": {
        "path": "/request",
        "accountId": "123456789012",
        "resourceId": "05c7jb",
        "stage": "test",
        "requestId": "...",
        "identity": {
            "apiKey": "...",
            "sourceIp": "..."
        },
        "resourcePath": "/request",
        "httpMethod": "GET",
        "apiId": "s4x3opwd6i"
    }
}
```

As far as looking, REQUEST stuff does not exactly cover Token stuff. Therefore we had better adopt the plan which adding an optional parameter in serverless.yml something like this. Off course leave TOKEN as the default.

``` yaml
authorizer:
  name: authorizerFunc
  type: token or request
```
+1 for this.

I ran into limitations with `token` custom authorizer myself on Friday.

The `type: request` option here would solve it
Can we do this please! It's a huge brick wall
As a bit of extra info, this becomes important if you're deploying individual services and aggregating under a single custom domain using basepaths. In this instance the TOKEN authoriser doesn't receive any information about the basepath. So if I have a `userService` mounted on the `/users` basepath, if I see a request for `/users/token` as an example, the custom authoriser can only see evidence of a request for `/token` in the methodARN of the `event` object.

Before I started breaking my project smaller services (https://github.com/serverless/serverless/issues/3411) this wasn't an issue - my authoriser was able to look at the methodARN and compare to permissions contained in the token. Unfortunately with the basepath approach, my authoriser which sees a permission for `GET::users:token` can't tell the request was for the `/users` basepath.

With the advent of REQUEST authoriser type I can deal with this issue, so just need support for this type in serverless :-)

Solution patch:
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index ff68cfbc3cc..aa593e44713 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -277,6 +277,7 @@ functions:
             resultTtlInSeconds: 0
             identitySource: method.request.header.Authorization
             identityValidationExpression: someRegex
+            type: token
   authorizerFunc:
     handler: handler.authorizerFunc
 ```
@@ -313,6 +314,24 @@ functions:
             identityValidationExpression: someRegex
 ```
 
+You can also use the Request Type Authorizer by setting the `type` property. In this case, your `identitySource` could contain multiple entries for you policy cache. The default `type` is 'token'.
+
+```yml
+functions:
+  create:
+    handler: posts.create
+    events:
+      - http:
+          path: posts/create
+          method: post
+          authorizer:
+            arn: xxx:xxx:Lambda-Name
+            resultTtlInSeconds: 0
+            identitySource: method.request.header.Authorization, context.identity.sourceIp
+            identityValidationExpression: someRegex
+            type: request
+```
+
 You can also configure an existing Cognito User Pool as the authorizer, as shown
 in the following example:
 
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js
index 84b86168429..e041f45fbbe 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js
@@ -37,7 +37,7 @@ module.exports = {
               '/invocations',
             ],
           ] };
-          authorizerProperties.Type = 'TOKEN';
+          authorizerProperties.Type = authorizer.type ? authorizer.type.toUpperCase() : 'TOKEN';
         }
 
         _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js
index 1d7e55b9d34..247d07367c1 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js
@@ -251,6 +251,10 @@ module.exports = {
         throw new this.serverless.classes.Error('Please provide either an authorizer name or ARN');
       }
 
+      if (!type) {
+        type = authorizer.type;
+      }
+
       resultTtlInSeconds = Number.parseInt(authorizer.resultTtlInSeconds, 10);
       resultTtlInSeconds = Number.isNaN(resultTtlInSeconds) ? 300 : resultTtlInSeconds;
       claims = authorizer.claims || [];


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js
index aa32f062c91..ee84ad8a24b 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js
@@ -96,6 +96,53 @@ describe('#compileAuthorizers()', () => {
     });
   });
 
+  it('should apply optional provided type value to Authorizer Type', () => {
+    awsCompileApigEvents.validated.events = [{
+      http: {
+        path: 'users/create',
+        method: 'POST',
+        authorizer: {
+          name: 'authorizer',
+          arn: 'foo',
+          resultTtlInSeconds: 500,
+          identityValidationExpression: 'regex',
+          type: 'request',
+        },
+      },
+    }];
+
+    return awsCompileApigEvents.compileAuthorizers().then(() => {
+      const resource = awsCompileApigEvents.serverless.service.provider
+        .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
+
+      expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
+      expect(resource.Properties.Type).to.equal('REQUEST');
+    });
+  });
+
+  it('should apply TOKEN as authorizer Type when not given a type value', () => {
+    awsCompileApigEvents.validated.events = [{
+      http: {
+        path: 'users/create',
+        method: 'POST',
+        authorizer: {
+          name: 'authorizer',
+          arn: 'foo',
+          resultTtlInSeconds: 500,
+          identityValidationExpression: 'regex',
+        },
+      },
+    }];
+
+    return awsCompileApigEvents.compileAuthorizers().then(() => {
+      const resource = awsCompileApigEvents.serverless.service.provider
+        .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
+
+      expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
+      expect(resource.Properties.Type).to.equal('TOKEN');
+    });
+  });
+
   it('should create a valid cognito user pool authorizer', () => {
     awsCompileApigEvents.validated.events = [{
       http: {
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js
index 6e9c6a12b1b..15fae5f2274 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js
@@ -406,6 +406,33 @@ describe('#validate()', () => {
     expect(authorizer.identityValidationExpression).to.equal('foo');
   });
 
+  it('should accept authorizer config with a type', () => {
+    awsCompileApigEvents.serverless.service.functions = {
+      foo: {},
+      first: {
+        events: [
+          {
+            http: {
+              method: 'GET',
+              path: 'foo/bar',
+              authorizer: {
+                name: 'foo',
+                type: 'request',
+                resultTtlInSeconds: 500,
+                identitySource: 'method.request.header.Custom',
+                identityValidationExpression: 'foo',
+              },
+            },
+          },
+        ],
+      },
+    };
+
+    const validated = awsCompileApigEvents.validate();
+    const authorizer = validated.events[0].http.authorizer;
+    expect(authorizer.type).to.equal('request');
+  });
+
   it('should accept authorizer config when resultTtlInSeconds is 0', () => {
     awsCompileApigEvents.serverless.service.functions = {
       foo: {},