{"action":"created","issue":{"url":"https://api.github.com/repos/awslabs/serverless-application-model/issues/512","repository_url":"https://api.github.com/repos/awslabs/serverless-application-model","labels_url":"https://api.github.com/repos/awslabs/serverless-application-model/issues/512/labels{/name}","comments_url":"https://api.github.com/repos/awslabs/serverless-application-model/issues/512/comments","events_url":"https://api.github.com/repos/awslabs/serverless-application-model/issues/512/events","html_url":"https://github.com/awslabs/serverless-application-model/issues/512","id":341672319,"node_id":"MDU6SXNzdWUzNDE2NzIzMTk=","number":512,"title":"RFC: Authorizers","user":{"login":"brettstack","id":1882775,"node_id":"MDQ6VXNlcjE4ODI3NzU=","avatar_url":"https://avatars0.githubusercontent.com/u/1882775?v=4","gravatar_id":"","url":"https://api.github.com/users/brettstack","html_url":"https://github.com/brettstack","followers_url":"https://api.github.com/users/brettstack/followers","following_url":"https://api.github.com/users/brettstack/following{/other_user}","gists_url":"https://api.github.com/users/brettstack/gists{/gist_id}","starred_url":"https://api.github.com/users/brettstack/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brettstack/subscriptions","organizations_url":"https://api.github.com/users/brettstack/orgs","repos_url":"https://api.github.com/users/brettstack/repos","events_url":"https://api.github.com/users/brettstack/events{/privacy}","received_events_url":"https://api.github.com/users/brettstack/received_events","type":"User","site_admin":false},"labels":[{"id":797198221,"node_id":"MDU6TGFiZWw3OTcxOTgyMjE=","url":"https://api.github.com/repos/awslabs/serverless-application-model/labels/type/design","name":"type/design","color":"1d76db","default":false}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":22,"created_at":"2018-07-16T20:44:23Z","updated_at":"2018-08-08T04:48:42Z","closed_at":"2018-08-08T04:48:42Z","author_association":"CONTRIBUTOR","body":"Resources:\r\nhttps://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-method.html#cfn-apigateway-method-authorizationtype\r\n\r\nhttps://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-authorizer.html\r\n\r\nRFC Closure date: August 03 2018\r\n\r\nAPI Gateway Authorizers come in three flavors - Cognito, Request Lambda, and Token Lambda. Both flavors of Lambda are very similar (TOKEN allows using a single header; REQUEST allows using multiple query strings, headers, stage variables, and \"context\"; these are sent as IdentitySource which also forms the cache key). We should be able to infer the Authorizer type based on the properties of the Authorizer, and allow the user to specify the `Type` when necessary.\r\n\r\nAuthorizers are unique compared to other things we've supported so far in SAM since they can be applied to multiple paths/methods of an API. As modelled in API Gateway, Authorizers are a child resource of an API, however, we don't need to model it that way in SAM. That is, Authorizers can either be defined as a property of a Serverless::API or established as an entirely new SAM Resource which can be used across multiple APIs.\r\n\r\nThe main benefit of defining Authorizers directly on the Serverless::API Resource is it is simpler/more concise. It also maps cleanly to API Gateway modeling which helps ensure future-proofing.\r\n\r\n```yaml\r\nGlobals:\r\n  Api:\r\n    Auth: # We will also define other auth options here such as Usage Plans/Api Keys, AWS_IAM, and Resource Policies\r\n      Authorizers: # APIs can define multiple Authorizers\r\n        MyAuth: # Authorizer properties will be discuused separately\r\n          ... # Will go into detail on properties in a separate section\r\n      DefaultAuthorizer: MyAuth # Optional; This will be used as the default Authorizer for all Resources/Methods; Alternate name: MainAuthorizer?\r\nResources:\r\n  ServerlessRestApi: # This is the implicit API resource created when you don't specify an `ApiId` in an Api Event.\r\n    Type: \"AWS::Serverless::API\"\r\n    Properties:\r\n      Auth:\r\n        Authorizers:\r\n          MyLambdaAuth:\r\n            ...\r\n          MyCognitoAuth:\r\n            ...\r\n        DefaultAuthorizer: MyLambdaAuth # NOTE: We are using the Authorizer name directly here (instead of !Ref/!GetAtt) since these are created in Swagger, not as CloudFormation resources.\r\n  MyFn:\r\n    Type: AWS::Serverless::Function\r\n    Properties:\r\n      ...\r\n      Events:\r\n        GetRoot:\r\n          Type: Api\r\n          Properties:\r\n            Method: get\r\n            Path: /\r\n            Auth:\r\n              Authorizer: MyCognitoAuth # Overrides the API's DefaultAuthorizer; can also specify null to make it open (null is default when no DefaultAuthorizer is specified)\r\n```\r\n\r\nThe benefit of creating a new Serverless::Authorizer resource is it allows sharing Authorizers across multiple APIs*, and allows defining Authorizers for the Implicit API without needing to define the implicit `ServerlessRestApi` resource (which isn't obvious to users). The disadantages are it's less concise, and would increase the confusion around not referencing with !Ref/!GetAtt (the standar way you reference all other Resources).\r\n\r\n* You can achieve sharing of Authorizers with the other approach by defining them in Globals. However, this only works when the Authorizers are the same across the APIs which you want to share Authorizers; i.e. if you need to define an additional Authorizer, it will override all Authorizers defined in Globals.\r\n\r\n```yaml\r\nGlobals:\r\n  Api:\r\n    DefaultAuthorizer: MyCognitoAuth\r\n  ApiAuthorizer:\r\n    IdentityValidationExpression: abc_.*\r\nResources:\r\n  MyCognitoAuth:\r\n    Type: \"AWS::Serverless::ApiAuthorizer\" # NOTE: Authorizers will be generated as part of the Swagger document, not an AWS::ApiGateway::Authorizer CFN resource which you might expect when seeing this defined as a Resource like this.\r\n    Properties:\r\n      ...\r\n  MyApi:\r\n    Type: \"AWS::Serverless::API\"\r\n    Properties:\r\n      Auth:\r\n        DefaultAuthorizer: MyCognitoAuth # Again, we are referencing direclty by name without using !Ref, which is what you would expect when referencing a Resource.\r\n```\r\n\r\nI am inclined for the former syntax defining Authorizers directly on an Api. The syntax is cleaner; maps to API Gateway modeling; and the lack of !Ref/!GetAtt will be less confusing. Exposing the Implicit API Resource is not desirable, but for most advanced use-cases users will define their own Api, making this a non-issue. I don't have any data on how useful \"Shareable API Authorizers\" is, but hopefully using Globals will be acceptable for most.\r\n\r\nI'll use my preferred syntax for describing an Authorizers properties, but they should remain the same for whichever syntax we agree on.\r\n\r\n```yaml\r\nResources:\r\n  MyApi:\r\n    Type: AWS::Serverless::API\r\n    Properties:\r\n      Auth:\r\n        Authorizers:\r\n          MyCognitoAuth:\r\n            Properties:\r\n              Type: COGNITO_USER_POOLS # Optional; inferred due to existence of `UserPools`\r\n              Name: ... # Optional; default to MyCognitoAuth (since Authorizer names don't need to be unique across APIs) or MyApiMyCognitoAuth (prefix with API name for consistency; likely be an issue when specifying Global Authorizers)\r\n              UserPool: !GetAtt MyUserPool.Arn # Must specify either UserPool or UserPools\r\n              UserPools: # Most of the time users will want to integrate with just a single UserPool; should we have 2 separate properties? Or a single `UserPools` which accepts both string and Array<string>?\r\n                - !GetAtt MyUserPool.Arn\r\n              RequestHeader: Authorization # is \"Authorization\" a sane default header? Sets IdentitySource to method.request.header.Authorization\r\n              RequestHeaderValidationExpression: ... # Optional; ensures the request header matches a pattern before checking in with the Authorizer endpoint; is there a default we can set for Cognito User Pools Auth?\r\n\r\n          MyLambdaTokenAuth:\r\n            Properties:\r\n              Type: LAMBDA_TOKEN # Optional; inferred due to existence of `RequestHeader`\r\n              FunctionArn: !GetAtt MyAuthFn.Arn # Potential future enhancement might be to specify `Function` directly on the Authorizer\r\n              RequestHeader: Authorization\r\n              InvokeWithCredentials: ... # Optional; null (default) | arn:aws:iam::123456789012:role/S3Access | S3Access; NOTE: CALLER_CREDENTIALS is not a valid value here unlike the proposed InvokeWithCredentials on an Integration\r\n              ReauthorizeEverySeconds: ... # Optional; Default: 300 (5 minutes); 0 means the authorizer will be called on every request\r\n              RequestHeaderValidationExpression: ... # Optional\r\n\r\n          MyLambdaRequesAuth:\r\n            Properties:\r\n              Type: LAMBDA_REQUEST # Optional; inferred due to existence of `RequestHeaders`, `RequestQueryStrings`, `RequestStageVariables`, or `RequestContext`\r\n              FunctionArn: !GetAtt MyAuthFn.Arn\r\n              RequestHeaders:\r\n                - Authorization1 # method.request.header.Authorization1\r\n              RequestQueryStrings:\r\n                - Authorization2 # method.request.querystring.Authorization2\r\n              RequestStageVariables: # \"Request\" doesn't make much sense for StageVariables or Context; I use it for consistency's sake\r\n                - Authorization3 # stageVariables.Authorization3\r\n              RequestContext:\r\n                - Authorization4 #context.Authorization4\r\n              # end result would be `IdentitySource: method.request.header.Authorization1,method.request.querystring.Authorization2,stageVariables.Authorization3,stageVariables.Authorization3`\r\n              InvokeWithCredentials: ... # Optional\r\n              ReauthorizeEverySeconds: ... # Optional\r\n              RequestHeaderValidationExpression: ... # Optional\r\n```\r\n\r\nShould we stick with `IdentitySource*` as it exists in CloudFormation instead of renaming it to `Request*`? I think the latter is easier to understand in most cases, e.g. IdentitySourceHeader vs RequestHeader. Happy to hear alternatives."},"comment":{"url":"https://api.github.com/repos/awslabs/serverless-application-model/issues/comments/411284092","html_url":"https://github.com/awslabs/serverless-application-model/issues/512#issuecomment-411284092","issue_url":"https://api.github.com/repos/awslabs/serverless-application-model/issues/512","id":411284092,"node_id":"MDEyOklzc3VlQ29tbWVudDQxMTI4NDA5Mg==","user":{"login":"brettstack","id":1882775,"node_id":"MDQ6VXNlcjE4ODI3NzU=","avatar_url":"https://avatars0.githubusercontent.com/u/1882775?v=4","gravatar_id":"","url":"https://api.github.com/users/brettstack","html_url":"https://github.com/brettstack","followers_url":"https://api.github.com/users/brettstack/followers","following_url":"https://api.github.com/users/brettstack/following{/other_user}","gists_url":"https://api.github.com/users/brettstack/gists{/gist_id}","starred_url":"https://api.github.com/users/brettstack/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brettstack/subscriptions","organizations_url":"https://api.github.com/users/brettstack/orgs","repos_url":"https://api.github.com/users/brettstack/repos","events_url":"https://api.github.com/users/brettstack/events{/privacy}","received_events_url":"https://api.github.com/users/brettstack/received_events","type":"User","site_admin":false},"created_at":"2018-08-08T04:48:42Z","updated_at":"2018-08-08T04:48:42Z","author_association":"CONTRIBUTOR","body":"Final syntax:\r\n\r\n```yaml\r\nResources:\r\n  # Minimum config for each type of authorizer being applied to an entire API:\r\n  MyApiWithCognitoAuth:\r\n      Type: \"AWS::Serverless::Api\"\r\n      Properties:\r\n        Auth:\r\n          DefaultAuthorizer: MyCognitoAuth\r\n          Authorizers:\r\n            MyCognitoAuth:\r\n              UserPoolArn: !GetAtt MyUserPool.Arn\r\n\r\n  MyApiWithLambdaTokenAuth:\r\n      Type: \"AWS::Serverless::Api\"\r\n      Properties:\r\n        Auth:\r\n          DefaultAuthorizer: MyLambdaTokenAuth\r\n          Authorizers:\r\n            MyLambdaTokenAuth:\r\n              FunctionArn: !GetAtt MyAuthFn.Arn\r\n\r\n  MyApiWithLambdaRequestAuth:\r\n      Type: \"AWS::Serverless::Api\"\r\n      Properties:\r\n        Auth:\r\n          DefaultAuthorizer: MyLambdaRequestAuth\r\n          Authorizers:\r\n            MyLambdaRequestAuth:\r\n              FunctionPayloadType: REQUEST\r\n              FunctionArn: !GetAtt MyAuthFn.Arn\r\n              Identity:\r\n                Headers:\r\n                  - Authorization1\r\n  \r\n  # All configuration options:\r\n  MyApi:\r\n    Type: \"AWS::Serverless::Api\"\r\n    Properties:\r\n      Auth: # We will eventually define other auth options here such as Usage Plans/Api Keys, AWS_IAM, and Resource Policies\r\n        Authorizers:\r\n          MyCognitoAuth:\r\n            UserPoolArn: !GetAtt MyUserPool.Arn # Can be a string, or array<string>\r\n            Identity: # Optional\r\n              Header: ... # Optional; Default: Authorization\r\n              ValidationExpression: ...  # Optional; ensures the request header matches a pattern before checking in with the Authorizer endpoint; is there a default we can set for Cognito User Pools Auth?\r\n\r\n          MyLambdaTokenAuth:\r\n            FunctionPayloadType: TOKEN # Optional; default: TOKEN\r\n            FunctionArn: !GetAtt MyAuthFn.Arn\r\n            FunctionInvokeRole: ... # Optional; Default: \"NONE\" | arn:aws:iam::123456789012:role/S3Access | S3Access; NOTE: CALLER_CREDENTIALS is not a valid value here unlike the proposed (see IAM RFC) FunctionInvokeRole on an Integration\r\n            Identity: # Optional\r\n              Header: ... # Optional; Default: Authorization\r\n              ValidationExpression: ... # Optional\r\n              ReauthorizeEvery: ... # Optional; Default: 300 (5 minutes); 0 means the authorizer will be called on every request (i.e no caching)\r\n\r\n          MyLambdaRequestAuth:\r\n            FunctionPayloadType: REQUEST\r\n            FunctionArn: !GetAtt MyAuthFn.Arn\r\n            FunctionInvokeRole: ... # Optional\r\n            Identity:\r\n              Headers:\r\n                - Authorization1 # method.request.header.Authorization1\r\n              QueryStrings:\r\n                - Authorization2 # method.request.querystring.Authorization2\r\n              StageVariables:\r\n                - Authorization3 # stageVariables.Authorization3\r\n              Context:\r\n                - Authorization4 #context.Authorization4\r\n              # end result would be `IdentitySource: method.request.header.Authorization1,method.request.querystring.Authorization2,stageVariables.Authorization3,stageVariables.Authorization3`\r\n              ReauthorizeEvery: ... # Optional\r\n        DefaultAuthorizer: MyLambdaTokenAuth # Optional; This will be used as the default Authorizer for all Resources/Methods of this API\r\n\r\n  # Setting/Overriding a Method/Path's Authorizer:\r\n  MyFn:\r\n    Type: AWS::Serverless::Function\r\n    Properties:\r\n      ...\r\n      Events:\r\n        GetRoot:\r\n          Type: Api\r\n          Properties:\r\n            RestApiId: MyApi\r\n            Method: get\r\n            Path: /\r\n            Auth:\r\n              Authorizer: MyCognitoAuth # Overrides the API's DefaultAuthorizer; can also specify \"NONE\" to make it open (\"NONE\" is default when no DefaultAuthorizer is specified on the Api)\r\n\r\n# Globals; out of scope for initial launch\r\nGlobals:\r\n  Api:\r\n    Auth:\r\n      Authorizers: # Allows a way to share Authorizer settings across APIs\r\n        MyAuth:\r\n          ...\r\n      DefaultAuthorizer: MyAuth\r\n```"}}
{"actor":{"display_login":"brettstack"}}