I am trying to get an API Gateway/Lambda web application (python flask with serverless-wsgi) to use a Cognito federated identity pool to authenticate/authorize web clients. The underlying requirement is to get a number of apps to permit access only to authorized Salesforce or Office365 users; and I don’t want to write OpenID Connect code in each application, but want to outsource this pain to Cognito.
So I created a Salesforce identity provider in IAM, and a Cognito identity pool linked to this, following this guide. (Aside: unclear as to what the callback URL should be, but I just used my app URL with /callback
appended for now)
And I added to serverless.yml:
web:
handler: wsgi.handler
events:
- http:
path: /
method: ANY
authorizer:
arn: arn:aws:cognito-identity:eu-west-1:XXXX:identitypool/eu-west-1:YYYY
- http:
path: "{proxy+}"
method: ANY
authorizer:
arn: arn:aws:cognito-identity:eu-west-1:XXXX:identitypool/eu-west-1:YYYY
This is based on the example here for an existing Cognito User Pool, although I realise that a Cognito Identity Pool is different (nice explanation here)
Now when I do sls deploy
I get this error:
Serverless: Checking Stack update progress...
...........
Serverless: Operation failed!
Serverless Error ---------------------------------------
An error occurred: A3e1b0cdac43LambdaPermissionApiGateway - Unable to parse HTTP response content.
If I do SLS_DEBUG="*" sls deploy
then I get a slightly different error:
Serverless: Checking Stack update progress...
.........
Serverless: Operation failed!
Serverless Error ---------------------------------------
An error occurred: A3e1b0cdac43ApiGatewayAuthorizer - Invalid lambda function.
Stack Trace --------------------------------------------
ServerlessError: An error occurred: A3e1b0cdac43ApiGatewayAuthorizer - Invalid lambda function.
at provider.request.then (/usr/lib/node_modules/serverless/lib/plugins/aws/lib/monitorStack.js:112:33)
From previous event:
at AwsDeploy.monitorStack (/usr/lib/node_modules/serverless/lib/plugins/aws/lib/monitorStack.js:26:12)
at provider.request.then (/usr/lib/node_modules/serverless/lib/plugins/aws/lib/updateStack.js:84:30)
From previous event:
at AwsDeploy.update (/usr/lib/node_modules/serverless/lib/plugins/aws/lib/updateStack.js:84:8)
From previous event:
at AwsDeploy.BbPromise.bind.then (/usr/lib/node_modules/serverless/lib/plugins/aws/lib/updateStack.js:101:12)
From previous event:
at AwsDeploy.updateStack (/usr/lib/node_modules/serverless/lib/plugins/aws/lib/updateStack.js:95:8)
From previous event:
at AwsDeploy.BbPromise.bind.then (/usr/lib/node_modules/serverless/lib/plugins/aws/deploy/index.js:135:39)
From previous event:
at Object.aws:deploy:deploy:updateStack [as hook] (/usr/lib/node_modules/serverless/lib/plugins/aws/deploy/index.js:131:10)
at BbPromise.reduce (/usr/lib/node_modules/serverless/lib/classes/PluginManager.js:372:55)
From previous event:
at PluginManager.invoke (/usr/lib/node_modules/serverless/lib/classes/PluginManager.js:372:22)
at PluginManager.spawn (/usr/lib/node_modules/serverless/lib/classes/PluginManager.js:390:17)
at AwsDeploy.BbPromise.bind.then (/usr/lib/node_modules/serverless/lib/plugins/aws/deploy/index.js:101:48)
From previous event:
at Object.deploy:deploy [as hook] (/usr/lib/node_modules/serverless/lib/plugins/aws/deploy/index.js:97:10)
at BbPromise.reduce (/usr/lib/node_modules/serverless/lib/classes/PluginManager.js:372:55)
From previous event:
at PluginManager.invoke (/usr/lib/node_modules/serverless/lib/classes/PluginManager.js:372:22)
at PluginManager.run (/usr/lib/node_modules/serverless/lib/classes/PluginManager.js:403:17)
at variables.populateService.then (/usr/lib/node_modules/serverless/lib/Serverless.js:102:33)
at runCallback (timers.js:672:20)
at tryOnImmediate (timers.js:645:5)
at processImmediate [as _immediateCallback] (timers.js:617:5)
From previous event:
at Serverless.run (/usr/lib/node_modules/serverless/lib/Serverless.js:89:74)
at serverless.init.then (/usr/lib/node_modules/serverless/bin/serverless:42:50)
If I look in .serverless/cloudformation-template-update-stack.json
then it looks like it’s trying to use a custom lambda authorizer (although I really don’t grok cloudformation)
"A3e1b0cdac43ApiGatewayAuthorizer": {
"Type": "AWS::ApiGateway::Authorizer",
"Properties": {
"AuthorizerResultTtlInSeconds": 300,
"IdentitySource": "method.request.header.Authorization",
"Name": "a3e1b0cdac43",
"RestApiId": {
"Ref": "ApiGatewayRestApi"
},
"AuthorizerUri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
"arn:aws:cognito-identity:eu-west-1:XXXX:identitypool/eu-west-1:YYYY",
"/invocations"
]
]
},
"Type": "TOKEN"
}
},
So my problems are:
-
Is it even possible to use API Gateway with Cognito federated Identity Pool as an authorizer?
The API gateway documentation mentions user pools, but I can see no mention of identity pools.
However this AWS post suggests it’s possible. It looks like the OpenID token is somehow exchanged for an IAM token, in which case maybe I can use aws_iam as the authorizer.
This post shows an OpenID identity pool. The app itself still needs to have its own login and callback pages which understand OpenID exchanges. I was hoping to make it as transparent to the app itself as possible, but I can give them a special login page if required.
-
If it is possible, does serverless support this, and if so how do I configure it?
Thanks… Brian.