Share authorizer with TOKEN type

api-gateway
cloudformation
aws
#1

Hello,

I can’t figure out how to setup a shared lambda authorizer of TOKEN type. I’ve read the documentation here https://serverless.com/framework/docs/providers/aws/events/apigateway#share-authorizer and some forums but i cannot find a way to do this.

I’ve made the following code but i have a Cannot read property 'Fn::Join' of undefined error :

functions:
  authorizer:
    handler: authorizer.handler

  hello:
    handler: hello.handler
    events:
      - http:
          path: hello
          method: get
          authorizer:
            type: TOKEN
            authorizerId:
              Ref: Authorizer

resources:
  Resources:
    ApiGatewayRestApi:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: ${self:service}

    Authorizer:
      DependsOn:
        - ApiGatewayRestApi
      Type: AWS::ApiGateway::Authorizer
      Properties:
        Name: Authorizer
        Type: TOKEN
        AuthorizerResultTtlInSeconds: 300
        RestApiId:
          Ref: ${self:service}

  Outputs:
    AuthorizerRef:
      Value:
        Ref: Authorizer
      Export:
        Name: authorizer-ref-${self:provider.stage}

Does anyone achieved to setup a shared lambda authorizer ?

Thanks in advance for any help !

#2

Here is my config

Resources:
    DefaultAuthorizer:
        Type: AWS::ApiGateway::Authorizer
        Properties:
            AuthorizerResultTtlInSeconds: 300
            AuthorizerUri:
                Fn::Join:
                    - ''
                    - - 'arn:aws:apigateway:'
                      - !Ref AWS::Region
                      - ':lambda:path/2015-03-31/functions/'
                      - !GetAtt DefaultDashauthorizerLambdaFunction.Arn
                      - '/invocations'
            Type: 'TOKEN'
            IdentitySource: 'method.request.header.Authorization'
            IdentityValidationExpression: .+
            Name: ${self:provider.stage}-DefaultAuthorizer
            RestApiId:
                Ref: 'MyApiGateway'

    DefaultAuthorizerPermission:
        Type: 'AWS::Lambda::Permission'
        Properties:
            Action: lambda:InvokeFunction
            FunctionName:
                Fn::GetAtt:
                    - 'DefaultDashauthorizerLambdaFunction'
                    - 'Arn'
            Principal: apigateway.amazonaws.com

Outputs:
    DefaultAuthorizer:
        Value:
            Ref: DefaultAuthorizer
        Export:
            Name: ${self:provider.stage}-DefaultAuthorizerId

As for DefaultDashauthorizerLambdaFunction, my authorizer function name is default-authoriser, that’s why I need to use Dash instead of ‘-’. In your case you shoul use AuthorizerLambdaFunction

in a different package (stack) I import it in this way

custom:
   defaultAuthorizerId: !ImportValue ${self:provider.stage}-DefaultAuthorizerId
...
functions:
   my-function:
        handler: services/my-functioni/index.handler
        events:
            - http:
                  path: events
                  method: post
                  authorizer:
                      type: CUSTOM
                      authorizerId: ${self:custom.defaultAuthorizerId}
#3

Thanks you very much for the answer. However it doesn’t seems to work on my side, i have this error :

The CloudFormation template is invalid: Unresolved resource dependencies [Authorizer] in the Outputs block of the template

I use serverless alias plugin to handle my stages, do you think it could come from this plugin ?

Thanks in advance !

#4

As for alias plugin - I don’t know. Can you provide your updated serverless.yml?

#5

Hello,

I’ve managed to export the authorizer without serverless aws alias plugin and the export shows in Cloudformation to confirm that it’s been exported. This is my serverless.yml :

functions:
  authorizer:
    handler: authorizer.handler

  hello:
    handler: hello.handler
    events:
      - http:
          path: hello
          method: get
          authorizer:
            type: CUSTOM
            authorizerId:
              Ref: Authorizer

resources:
  Resources:
    AuthorizerPermission:
      Type: AWS::Lambda::Permission
      Properties:
          FunctionName:
              Fn::GetAtt: AuthorizerLambdaFunction.Arn
          Action: lambda:InvokeFunction
          Principal:
            Fn::Join: ["",["apigateway.", { Ref: "AWS::URLSuffix"}]]
    Authorizer:
      DependsOn:
        - ApiGatewayRestApi
      Type: AWS::ApiGateway::Authorizer
      Properties: 
        Name: ${self:provider.stage}-Authorizer
        RestApiId: { "Ref" : "ApiGatewayRestApi" }
        Type: TOKEN
        IdentitySource: method.request.header.Authorization
        AuthorizerResultTtlInSeconds: 300
        AuthorizerUri:
          Fn::Join:
            - ''
            - 
              - 'arn:aws:apigateway:'
              - Ref: "AWS::Region"
              - ':lambda:path/2015-03-31/functions/'
              - Fn::GetAtt: "AuthorizerLambdaFunction.Arn"
              - "/invocations"

  Outputs:
    AuthorizerId:
      Value:
        Ref: Authorizer
      Export:
        Name: ${self:service}-${self:provider.stage}-AuthorizerId

However, when i try to import it inside another service i have an error :

An error occurred: ApiGatewayMethodV1SomeGet - Invalid authorizer ID specified. Setting the authorization type to CUSTOM or COGNITO_USER_POOLS requires a valid authorizer. (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException).

I’ve tried to import it like this :

# inside some http get event
authorizer:
  type: CUSTOM
  authorizerId: ${cf:authorizer-apigateway-${self:provider.stage}.AuthorizerId}

Do you have any idea where this error could come from ?

Thanks a lot in advance ! :slight_smile:

#6

It seems to me that you use AuthorizerId instead of an export name, so I think the right ref should be
${cf:authorizer-apigateway-${self:provider.stage}.authorizer-apigateway-${self:provider.stage}-AuthorizerId}, because the correct format is ${cf:STACK_NAME.EXPORT_VALUE_NAME}
Can you check it?
and also please try to use this approach

authorizer:
  type: CUSTOM
  authorizerId: !ImportValue authorizer-apigateway-${self:provider.stage}-AuthorizerId

p.s. I’ve assumed that your common package name is authorizer-apigateway and your stack name is authorizer-apigateway-${self:provider.stage}

#7

Thanks a lot for the answer but it doesn’t seems to work, i have the same error message :confused:

Nevertheless i might know where this error comes from. I used separate services (so separated api gateway) so the second service might not have the right to access the first service. If i export the restApiId of the first service and import it in the second service do you think it might work ?

#8

Honestly, I don’t know (I assume that this will not help), but I would recommend trying a few things.
open your cloudformation console and take a look at your exported variables (here is an example https://take.ms/HBR2J), you will see your authorizer exported name.
1 Try to hardcode your auhorizerId import, you will have something like this authorizerId: !ImportValue authorizer-apigateway-stage-AuthorizerId, if it works - it means that your import ${cf:...} is not correct.
2. Try to hardcode a value (you will see it in the outputs tab), you will have authorizerId: d242dsf2, and you will be able to check if your authorizer works

if it doesn’t help - maybe we can talk via skype or another messenger, in this way, we will fix it much faster. but it’s up to you

#9

Ok i quite solved my problem by making the 2 stacks (serverless service) share a unique Api Gateway. Here is my final code :

service: authorizer-stack

functions:
 authorizer:
   handler: authorizer.handler

 hello:
   handler: hello.handler
   events:
     - http:
         path: hello
         method: get
         authorizer:
           type: CUSTOM
           authorizerId:
             Ref: Authorizer

resources:
 Resources:
   AuthorizerPermission:
     Type: AWS::Lambda::Permission
     Properties:
         FunctionName:
             Fn::GetAtt: AuthorizerLambdaFunction.Arn
         Action: lambda:InvokeFunction
         Principal:
           Fn::Join: ["",["apigateway.", { Ref: "AWS::URLSuffix"}]]
   Authorizer:
     DependsOn:
       - ApiGatewayRestApi
     Type: AWS::ApiGateway::Authorizer
     Properties: 
       Name: ${self:provider.stage}-Authorizer
       RestApiId: { "Ref" : "ApiGatewayRestApi" }
       Type: TOKEN
       IdentitySource: method.request.header.Authorization
       AuthorizerResultTtlInSeconds: 300
       AuthorizerUri:
         Fn::Join:
           - ''
           - 
             - 'arn:aws:apigateway:'
             - Ref: "AWS::Region"
             - ':lambda:path/2015-03-31/functions/'
             - Fn::GetAtt: "AuthorizerLambdaFunction.Arn"
             - "/invocations"

 Outputs:
   AuthorizerId:
     Value:
       Ref: Authorizer
     Export:
       Name: ${self:service}-${self:provider.stage}-authorizerId
   apiGatewayRestApiId:
     Value:
       Ref: ApiGatewayRestApi
     Export:
       Name: ${self:service}-${self:provider.stage}-restApiId
   apiGatewayRestApiRootResourceId:
     Value:
        Fn::GetAtt:
         - ApiGatewayRestApi
         - RootResourceId
     Export:
       Name: ${self:service}-${self:provider.stage}-rootResourceId

And then import it

provider:
  # Next lines enable same api gateway usage but different cloudformation stacks
  apiGateway:
    restApiId:
      Fn::ImportValue: authorizer-stack-${self:provider.stage}-restApiId
    restApiRootResourceId:
      Fn::ImportValue: authorizer-stack-${self:provider.stage}-rootResourceId
functions:
  helloWorld:
    handler: hello.handler
    events:
      - http:
          path: hello-world
          method: get
          authorizer:
            type: CUSTOM
            authorizerId: !ImportValue authorizer-stack-${self:provider.stage}-authorizerId

Nevertheless this solution doesn’t fit my needs. I would like to have different Apis of ApiGateway sharing the same authorizer so when i change the configuration of the authorizer, all Apis are affected.

Do you think it could be possible, even inside the aws console ? Otherwise i will submit a feature request to AWS :wink:

In all cases, thanks for your very generous help !

#10

Hi, haven’t seen your message. I don’t think that you can share authorizers between different api gateways. As you can see each gateway has its own list of authorizers and when you declare your authorizer you also need to set a RestApiId property.
As I can see you have a few options:

  1. Use the same api gateway for all your services (you don’t want to do it :slight_smile: )
  2. Extract your authorizer code to a separate package and use this code in all your api gateways (you will have as many authorizers as many gateways you have), but when you change your authorizer code - you will need to redeploy all your api authorizers.