Comments (26)
Looking closely at the auth@edge 2.0 code, I think it can go wrong if you:
- set parameter
CreateCloudFrontDistribution
tofalse
- AND
- do not provide a value for parameter
AlternateDomainNames
Which is indeed the scenario you're in, looking at that piece of CFN you pasted.
You can unblock yourself by providing AlternateDomainNames or by setting CreateCloudFrontDistribution to true.
Meanwhile, I will fix the code, to make this scenario work again too.
Question: is this just a test or do you actually want to deploy like this? Because in this scenario you always have to manually update the redirect URI's in the User Pool Domain, after doing the deployment.
from cloudfront-authorization-at-edge.
This isn't our most common deploy scenario -- the common usage is to use an AlternateDomainName
-- but it's a useful testing scenario for a quick setup that doesn't require a certificate and domain, and for us a required scenario for an agency or company that does not have control over their domains or certificates..
So we would like to be able to both create the cloudfront in the top level stack, setting CreateCloudFrontDistribution
to false and providing no value for AlternateDomainName
. One original hope in testing a@e 2.0 was that in bringing both the UserPool and the Cloudfront inside the top level CFN stack we could avoid a circular dependency by creating the CloudFront (thus making its entry point available to CFN) then using the Cloudfront entry point as the redirect URI in the User Pool.
Unfortunately it looks like a@e needs the Cloudfront default URL, and CloudFront needs to know the URLs of the a@e lambdas so there is still a circularity.
Failing a true solution it would be very welcome to restore the 1.2 capability to allow no AlternateDomainName and CreateCloudFrontDistribution to False, then manually update the user pool.
Eventually we may rewrite to avoid the top level CloudFormation stack altogether in favor of our internal cloud API tool called mu -- but that's a long term prospect.
from cloudfront-authorization-at-edge.
Unfortunately it looks like a@e needs the Cloudfront default URL, and CloudFront needs to know the URLs of the a@e lambdas so there is still a circularity.
Yes that can only be solved by a custom resource in the top level stack. But the custom resource implementation can be borrowed from a@e, so it will be simple actually. But the a@e custom resource handler arn needs to be made a stack output, so you can refer to it (a 1 min change to a@e)
from cloudfront-authorization-at-edge.
Funny, I was out walking the dog and the same idea occurred to me. There's already a LambdaCodeUpdateHandler
lambda in a@e that fills the architectural function that would be required in a custom resource, since the LambdaCodeUpdateHandler
adjusts the JSON in the other lambdas for changes in headers and the like!
That would be a great solution ... and I hope it's something you might do. I'm seeing it like this -- just to get it on paper:
- CloudFront and UserPool related resources can live in main stack
- a@e will tolerate an absent
AlternateDomainName
and a falseCreateCloudFrontDistribution
, and if nothing more is done, manual intervention on the user pool will be required, similar to 1.2. That's the part of the discussion that restores the scenario present in 1.2 but not in 2.0
In addition, we can add functionality to significantly make the stacks more independent as you suggest:
- In the top level stack a@e is invoked before CloudFront, and yields the outputs CloudFront needs for its behaviours, same as always
- The a@e lambdas that need to know the URL of the CloudFront application refactor that value into JSON for ease of modification if it isn't there already
- The existing
LambdaCodeUpdateHandler
is enhanced to allow modifying the CloudFront URL wherever it appears LambdaCodeUpdateHandler
's Arn is made available as an a@e stack output so it can be invoked from the top level stack- The top level stack contains a new custom resource dependent on CloudFront that invokes the enhanced
LambdaCodeUpdateHandler
with the CloudFront URL
Is that what you had in mind? Is that possible? If so I'd be happy to test it out.
R.
from cloudfront-authorization-at-edge.
Funny, I was out walking the dog and the same idea occurred to me.
LOL!
Yeah that's it in principle––but I mean the UserPoolClientUpdate custom resource to be exact. I need to have a step back to see how this resource is used now and if the plan really makes sense, but I think it does. I'll keep you posted.
If you wanna do it by the way, that's fine too. I can provide guidance while you code the PR (if you need it)
from cloudfront-authorization-at-edge.
I'd rather you did if you're willing ... I'm pretty snowed on the app itself, and don't have any experience compiling a SAM module.
from cloudfront-authorization-at-edge.
This should be it: #83
from cloudfront-authorization-at-edge.
OK you should now be able to add a resource like this to the top-level stack:
UserPoolClientUpdate:
Type: Custom::UserPoolClientUpdate
Condition: UpdateUserPoolClient
Properties:
ServiceToken: !GetAtt LambdaEdgeProtection.Outputs.UserPoolClientUpdateHandler
UserPoolArn: <your arn, or grab from Auth@Edge stack>
UserPoolClientId: <your client id, or grab from Auth@Edge stack>
CloudFrontDistributionDomainName: <your CloudFront domain>
RedirectPathSignIn: "/parseauth"
RedirectPathSignOut: "/"
AlternateDomainNames: []
OAuthScopes: <your scopes>
from cloudfront-authorization-at-edge.
v2.0.1 in the SAR
from cloudfront-authorization-at-edge.
It must be about a million o'clock in NE -- thanks! I'll test it right away
from cloudfront-authorization-at-edge.
Brilliant ... as we're in different time zones (I think), I'll give you preliminary results which are:
- My two test scenarios build and deploy. The first use case has no custom resource and I adjust the Cognito URIs by hand, the second uses the callback.
- Both build and complete without error after a little fiddling on my side on the top level CFN
- Both give an error from the lambdas that I don't understand at this point:
Error: [Cognito] invalid_request: invalid_scope [log region: us-east-1]
The only things in cloudtrail I immediately see seem to relate to parseAuth creating logs ...
"eventTime": "2020-09-07T21:52:50Z",
"eventSource": "logs.amazonaws.com",
"eventName": "CreateLogStream",
"awsRegion": "us-east-1",
"sourceIPAddress": "3.235.155.143",
"userAgent": "awslambda-worker/1.0 rusoto/0.42.0 rust/1.45.2 linux",
"errorCode": "ResourceNotFoundException",
"errorMessage": "The specified log group does not exist.",
"requestParameters": {
"logGroupName": "/aws/lambda/us-east-1.ae201nocustom-LambdaEdgeProtectio-ParseAuthHandler-S6CI0U5A5ZVZ",
"logStreamName": "2020/09/07/[1]35bbd71f99a34f9a8f1c4b2537020c06"
},
for scenario 1 (no custom) and
"eventTime": "2020-09-07T21:55:19Z",
"eventSource": "logs.amazonaws.com",
"eventName": "CreateLogStream",
"awsRegion": "us-east-1",
"sourceIPAddress": "3.237.173.225",
"userAgent": "awslambda-worker/1.0 rusoto/0.42.0 rust/1.45.2 linux",
"errorCode": "ResourceNotFoundException",
"errorMessage": "The specified log group does not exist.",
"requestParameters": {
"logStreamName": "2020/09/07/[1]1b33630fb1d14708be678dc689047831",
"logGroupName": "/aws/lambda/us-east-1.ae201customb-LambdaEdgeProtection-ParseAuthHandler-OT6ZOK55QK6B"
}
for scenario 2 (custom resource)
These may be unrelated. I'll dig in more in the AM, but wanted to give you early feedback as this looks very promising
from cloudfront-authorization-at-edge.
Oh yeah, I think it's something about the log group creation. the URL of the fail says clearly the problematic lambda is parseAuth: https://d8whov6mdjadw.cloudfront.net/parseauth?error_description=invalid_scope&state=eyJub25jZSI6IjE1OTk1MTU1NjRUaHAuQlM5Nm5wRUtuT0RYeiIsInJlcXVlc3RlZFVyaSI6Ii8ifQ&error=invalid_request
. When I navigate to CLoudwatch Events I see:
So maybe something funky with the group creation ... is there somewhere I should have specified a region now that we are no longer tied to us-east-1?
from cloudfront-authorization-at-edge.
The log group error might be a red herring––the real error looks to be "invalid_scope"
How are you passing the scopes to the custom resource? It expects them here as a list of strings (not as a CommaDelimitedList):
UserPoolClientUpdate:
Type: Custom::UserPoolClientUpdate
Condition: UpdateUserPoolClient
Properties:
ServiceToken: !GetAtt LambdaEdgeProtection.Outputs.UserPoolClientUpdateHandler
UserPoolArn: <your arn, or grab from Auth@Edge stack>
UserPoolClientId: <your client id, or grab from Auth@Edge stack>
CloudFrontDistributionDomainName: <your CloudFront domain>
RedirectPathSignIn: "/parseauth"
RedirectPathSignOut: "/"
AlternateDomainNames: []
OAuthScopes: ["profile", "openid", "email", "phone", "aws.cognito.signin.user.admin"]
That list needs to be the same as what you passed to Auth@Edge (if you did not pass anything it needs to be the same as the Auth@Edge default). But then it is probably easier to not provide the scopes when invoking the custom resource––and keep the ones from auth@edge. So, this should also work:
UserPoolClientUpdate:
Type: Custom::UserPoolClientUpdate
Condition: UpdateUserPoolClient
Properties:
ServiceToken: !GetAtt LambdaEdgeProtection.Outputs.UserPoolClientUpdateHandler
UserPoolArn: <your arn, or grab from Auth@Edge stack>
UserPoolClientId: <your client id, or grab from Auth@Edge stack>
CloudFrontDistributionDomainName: <your CloudFront domain>
RedirectPathSignIn: "/parseauth"
RedirectPathSignOut: "/"
AlternateDomainNames: []
from cloudfront-authorization-at-edge.
Well, not having a lot of luck here.
Originally, I was passing scopes like this to both the UserPoolClient and the UserPoolClientUpdate:
AllowedOAuthScopes:
- phone
- email
- openid
- profile
and the stack deployed, but the run failed, as noted. I believe the two formats should be the same ... but apparently not.
Substituting the corrected string format in both the UserPoolClient and the UserPoolClientUpdate allowed for a deploy, but I get the identical error on scope.
Adding the additional scope parameter aws.cognito.signin.user.admin
fixed that problem. Seems to allow a user to edit their own profile from this note. Was never needed before..
So, once I got past the scope error I was able to log into Cognito and change my password, but then encountered a new error in parseAuth:
Sign-in issue
We can't sign you in because of a technical problem
Error: Failed to exchange authorization code for tokens: Error: Request failed with status code 400 [log region: us-east-1]
Here are the two stanzas as they currently are ... note that the parameter names are different which seems to be required ... AllowedOAuthScopes
for the UserPoolClient and OAuthScopes
for UserPoolClientUpdate. Making them the same fails at deploy.
UserPoolClient:
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
PreventUserExistenceErrors: ENABLED
GenerateSecret: true
AllowedOAuthScopes: ["profile", "openid", "email", "phone", "aws.cognito.signin.user.admin"]
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthFlows:
- code
SupportedIdentityProviders:
- COGNITO
CallbackURLs:
- https://example.com/will-be-replaced
LogoutURLs:
- https://example.com/will-be-replaced
UserPoolClientUpdate:
UserPoolClientUpdate:
Type: Custom::UserPoolClientUpdate
Condition: UpdateUserPoolClient
Properties:
ServiceToken: !GetAtt LambdaEdgeProtection.Outputs.UserPoolClientUpdateHandler
UserPoolArn: !GetAtt UserPool.Arn
UserPoolClientId: !Ref UserPoolClient
CloudFrontDistributionDomainName: !GetAtt CloudFrontDistribution.DomainName
RedirectPathSignIn: "/parseauth"
RedirectPathSignOut: "/"
AlternateDomainNames: []
OAuthScopes: ["profile", "openid", "email", "phone", "aws.cognito.signin.user.admin"]
By the way, the OAuthScopes
param seems to be required in the UserPoolClientUpdate ... commenting it out leads to a deploy error on the custom resource:
Failed to update resource. InvalidParameterException: AllowedOAuthFlows and AllowedOAuthScopes are required if user pool client is allowed to use OAuth flows. at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:51:27) at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20) at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10) at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:688:14) at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10) at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12) at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10 at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9) at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:690:12) at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:1
from cloudfront-authorization-at-edge.
By the way2 , the scenario 1 (no custom resource, just cloudfront=false and no alternate domain) also fails, this time with an unspecified error after login password entered to cognito:
https://auth-3f673eb0-f150-11ea-a7c2-0a3fc8cbcb75.auth.us-east-1.amazoncognito.com/error
from cloudfront-authorization-at-edge.
Mmm :|
Can you paste your entire CFN template here, I'll try to reproduce
from cloudfront-authorization-at-edge.
Preparing a stripped version as the original accesses buckets, etc.
from cloudfront-authorization-at-edge.
Edited, simplified. Gives the error Failed to exchange authorization code for tokens: Error: Request failed with status code 400 [log region: us-east-1]
that we're working on.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
Sample stack for custom user pool
Parameters:
AlternateDomainNames:
Type: CommaDelimitedList
Description: "Optional custom domain name for the CloudFront distribution. Must manually point the custom name's A record to CloudFront URL as an alias after deploy."
ACMCertificate:
Type: String
Description: "Only if using an alternate domain name. ARN for a us-east-1 ACM certificate in this account. Leave blank if no alternate domain name"
Installer:
Type: String
Description: Who is installing this application, used for tags
PriceClass:
Type: String
Description: CloudFront price class, e.g. PriceClass_200 for most regions (default), PriceClass_All for all regions (the default), PriceClass_100 least expensive (US, Canada, Europe), or PriceClass_All
Default: PriceClass_100
SemanticVersion:
Type: String
Description: Semantic version of the back end
Default: 2.0.1
HttpHeaders:
Type: String
Description: The HTTP headers to set on all responses from CloudFront. Defaults are illustrations only and contain a report-only Cloud Security Policy -- adjust for your application
Default: >-
{
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"Referrer-Policy": "same-origin",
"X-XSS-Protection": "1; mode=block",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff"
}
BucketNameParameter:
Type: String
Description: A legal bucket name. Must not exist.
Conditions:
IsCommercial: !Equals [ 'aws', !Ref "AWS::Partition" ]
HasAlternateDomainName: !Not [!Equals [ '', !Join [ "", !Ref AlternateDomainNames ] ] ]
UpdateUserPoolClient: !Equals [ "true", "true" ]
Resources:
ApplicationBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName:
Ref: BucketNameParameter
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
CorsConfiguration:
CorsRules:
-
AllowedOrigins:
- !Sub
- "https://${DomainName}"
- {DomainName: !Select [0, !Ref AlternateDomainNames ] }
AllowedMethods:
- POST
- GET
- PUT
- DELETE
- HEAD
AllowedHeaders:
- "*"
Tags:
-
Key: "OWNER"
Value: "egt-labs"
-
Key: "APPLICATION"
Value: "jmpr"
-
Key: "PATH"
Value: {"Fn::Join": ["", ["/", {"Ref": "AWS::StackName"}, "-installer/"]]}
-
Key: "INSTALLER"
Value: !Ref Installer
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub '${BucketNameParameter}-OAI'
S3AccessPolicyForOAI:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: ApplicationBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
CanonicalUser:
Fn::GetAtt: [ CloudFrontOriginAccessIdentity , S3CanonicalUserId ]
Action: "s3:GetObject"
Resource: !Sub "${ApplicationBucket.Arn}/*"
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
!If
- HasAlternateDomainName
- !Ref AlternateDomainNames
- !Ref AWS::NoValue
ViewerCertificate:
!If
- HasAlternateDomainName
- AcmCertificateArn: !Ref ACMCertificate
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.2_2018
- !Ref AWS::NoValue
CacheBehaviors:
- PathPattern: /parseauth
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt LambdaEdgeProtection.Outputs.ParseAuthHandler
TargetOriginId: dummy-origin
ViewerProtocolPolicy: redirect-to-https
- PathPattern: /refreshauth
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt LambdaEdgeProtection.Outputs.RefreshAuthHandler
TargetOriginId: dummy-origin
ViewerProtocolPolicy: redirect-to-https
- PathPattern: /signout
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt LambdaEdgeProtection.Outputs.SignOutHandler
TargetOriginId: dummy-origin
ViewerProtocolPolicy: redirect-to-https
DefaultCacheBehavior:
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt LambdaEdgeProtection.Outputs.CheckAuthHandler
- EventType: origin-response
LambdaFunctionARN: !GetAtt LambdaEdgeProtection.Outputs.HttpHeadersHandler
TargetOriginId: protected-origin
ViewerProtocolPolicy: redirect-to-https
Enabled: true
Origins:
- DomainName: example.org # Dummy origin is used for Lambda@Edge functions, keep this as-is
Id: dummy-origin
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
- DomainName: !Sub "${ApplicationBucket}.s3.amazonaws.com"
Id: protected-origin
S3OriginConfig:
OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
CustomErrorResponses:
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
PriceClass: !Ref PriceClass
DefaultRootObject: index.html
Tags:
-
Key: "OWNER"
Value: "egt-labs"
-
Key: "APPLICATION"
Value: "jmpr"
-
Key: "PATH"
Value: {"Fn::Join": ["", ["/", {"Ref": "AWS::StackName"}, "-installer/"]]}
-
Key: "INSTALLER"
Value: !Ref Installer
CognitoIdentityPool:
Type: "AWS::Cognito::IdentityPool"
Properties:
IdentityPoolName: !Sub ${BucketNameParameter}-Identity
AllowUnauthenticatedIdentities: false
CognitoIdentityProviders:
- ClientId: !Ref UserPoolClient
# ProviderName: !Sub 'cognito-idp.${AWS::Region}.amazonaws.com/${LambdaEdgeProtection.Outputs.UserPoolId}'
ProviderName: !Sub
- cognito-idp.${AWS::Region}.amazonaws.com/${userpool}
- { userpool: !Ref UserPool }
# ProviderName: !Sub
# - 'cognito-idp.${AWS::Region}.amazonaws.com/${userpool}'
# - { userpool: !Ref UserPoolClient }
# ProviderName: !Sub 'cognito-idp.${AWS::Region}.amazonaws.com/${!Ref UserPool}'
# Create a role for unauthorized acces to AWS resources.
# Present only for illustration or possible future use. Not assigned to pool
CognitoUnAuthorizedRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Federated: "cognito-identity.amazonaws.com"
Action:
- "sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref CognitoIdentityPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": unauthenticated
Policies:
- PolicyName: "CognitoUnauthorizedPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "mobileanalytics:PutEvents"
- "cognito-sync:*"
Resource: "*"
Tags:
-
Key: "OWNER"
Value: "egt-labs"
-
Key: "APPLICATION"
Value: "jmpr"
-
Key: "PATH"
Value: {"Fn::Join": ["", ["/", {"Ref": "AWS::StackName"}, "-installer/"]]}
-
Key: "INSTALLER"
Value: !Ref Installer
# Create a role for authorized acces to AWS resources. Control what your user can access. This example only allows Lambda invokation
# Only allows users in the previously created Identity Pool
CognitoAuthorizedRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Federated: "cognito-identity.amazonaws.com"
Action:
- "sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref CognitoIdentityPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": authenticated
Policies:
- PolicyName: "BasicCognito"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "mobileanalytics:PutEvents"
- "cognito-sync:*"
- "cognito-identity:*"
Resource: "*"
- PolicyName: "jmprExecution"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "lambda:InvokeFunction"
Resource: "*"
- PolicyName: "jmprS3"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "s3:*"
Resource: "*"
Tags:
-
Key: "OWNER"
Value: "egt-labs"
-
Key: "APPLICATION"
Value: "jmpr"
-
Key: "PATH"
Value: {"Fn::Join": ["", ["/", {"Ref": "AWS::StackName"}, "-installer/"]]}
-
Key: "INSTALLER"
Value: !Ref Installer
# Assigns the roles to the Identity Pool
IdentityPoolRoleMapping:
Type: "AWS::Cognito::IdentityPoolRoleAttachment"
Properties:
IdentityPoolId: !Ref CognitoIdentityPool
Roles:
authenticated: !GetAtt CognitoAuthorizedRole.Arn
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Ref AWS::StackName
AdminCreateUserConfig:
AllowAdminCreateUserOnly: true
UsernameAttributes:
- email
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
PreventUserExistenceErrors: ENABLED
GenerateSecret: true
AllowedOAuthScopes: ["profile", "openid", "email", "phone", "aws.cognito.signin.user.admin"]
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthFlows:
- code
SupportedIdentityProviders:
- COGNITO
CallbackURLs:
# Ideally you would put your real callback URL's here, pointing to your custom domain name––the custom
# domain name that you would also supply as AlternateDomainNames to the cloudfront-authorization-at-edge stack below.
# However, if you just want to use the CloudFront domain name, use the sentinel value as below, to avoid a circular dependency.
# This sentinal value will be replaced automatically by the cloudfront-authorization-at-edge stack, with the CloudFront domain name
- https://example.com/will-be-replaced
LogoutURLs:
# Ideally you would put your real logout URL's here, pointing to your custom domain name––the custom
# domain name that you would also supply as AlternateDomainNames to the cloudfront-authorization-at-edge stack below.
# However, if you just want to use the CloudFront domain name, use the sentinel value as below, to avoid a circular dependency.
# This sentinal value will be replaced automatically by the cloudfront-authorization-at-edge stack, with the CloudFront domain name
- https://example.com/will-be-replaced
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
Domain: !Sub
- "auth-${StackIdSuffix}"
- StackIdSuffix: !Select
- 2
- !Split
- "/"
- !Ref AWS::StackId
UserPoolId: !Ref UserPool
LambdaEdgeProtection:
Type: AWS::Serverless::Application
DependsOn:
- UserPoolDomain
Properties:
Location:
ApplicationId: arn:aws:serverlessrepo:us-east-1:520945424137:applications/cloudfront-authorization-at-edge
SemanticVersion: !Ref SemanticVersion
Parameters:
CreateCloudFrontDistribution: "false"
HttpHeaders: !Ref HttpHeaders
UserPoolArn: !GetAtt UserPool.Arn
UserPoolClientId: !Ref UserPoolClient
UserPoolClientUpdate:
Type: Custom::UserPoolClientUpdate
Condition: UpdateUserPoolClient
Properties:
ServiceToken: !GetAtt LambdaEdgeProtection.Outputs.UserPoolClientUpdateHandler
UserPoolArn: !GetAtt UserPool.Arn
UserPoolClientId: !Ref UserPoolClient
CloudFrontDistributionDomainName: !GetAtt CloudFrontDistribution.DomainName
RedirectPathSignIn: "/parseauth"
RedirectPathSignOut: "/"
AlternateDomainNames: []
OAuthScopes: ["profile", "openid", "email", "phone", "aws.cognito.signin.user.admin"]
from cloudfront-authorization-at-edge.
Only stack parms needed are the bucket name and stack name
from cloudfront-authorization-at-edge.
I've found the culprit: in your template change GenerateSecret to false and redeploy and 🎉 it works.
Or, leave it to true, but disable SPA mode then for Auth@Edge: EnableSPAMode = false
I'll update the docs to point this out clearly (and fix the reuse example which was wrong).
Note: I found the cause by setting LogLevel to "debug" for Auth@Edge and checking what happened in parseAuth logs.
from cloudfront-authorization-at-edge.
Thanks! That's great news! Starting testing now. Some interesting questions:
- It looks like generateSecret is optional and allows the deployer to generate a secret on the client that the SPA can check to avoid impersonators. Am I right in guessing that the a@e doesn't use that feature yet, which is why it fails? It sounds useful at some point, but it's unclear how the SPA would know what to check the generated secret against, unless perhaps the UserPoolClient returned it on deploy as a configuration for the SPA. Is that right?
- What does it mean to run as EnableSPAMode as false when in fact you're a SPA. I worry about that one
- Where do you set the LogLevel on Auth@Edge? How come you get parseAuth logs and I just get the complaint about no log group?
Testing away, exciting!
from cloudfront-authorization-at-edge.
Updated this example: example-serverless-app-reuse/reuse-with-existing-user-pool.yaml
About your Q's:
It looks like generateSecret is optional and allows the deployer to generate a secret on the client that the SPA can check to avoid impersonators. Am I right in guessing that the a@e doesn't use that feature yet, which is why it fails? It sounds useful at some point, but it's unclear how the SPA would know what to check the generated secret against, unless perhaps the UserPoolClient returned it on deploy as a configuration for the SPA. Is that right?
What does it mean to run as EnableSPAMode as false when in fact you're a SPA. I worry about that one
Have a read of this: SPA mode or Static Site mode?
For SPAs it's not useful to have a Client Secret, as anyone can do "view source" and see it.
Where do you set the LogLevel on Auth@Edge? How come you get parseAuth logs and I just get the complaint about no log group?
It's a param to the app, just as CreateCloudFrontDistribution and EnableSPAMode:
cloudfront-authorization-at-edge/template.yaml
Lines 122 to 131 in 78867bb
To find the logs you have to go through hoops a bit, as Lambda@Edge logs get their own special log groups, in the region where they end up running (the "normal" log group of the Lambda would not show anything). Easiest way to find them is to go to the CloudFront monitoring dashboard, find the function invocations and jump to the log in the right region there.
from cloudfront-authorization-at-edge.
Thanks, makes sense. I took SPA mode too literally I think. For whatever reason I do not see the updates to example-serverless-app-reuse/reuse-with-existing-user-pool.yaml in the master branch.
Continuing testing
- Initial indications on template above with fix are positive
- Secondary test on our actual app with the custom resource are positive
There may be an issue on the 3rd test, which is the app without custom resources and a manual update to the Application Client callback URLs. Will pursue and get back
from cloudfront-authorization-at-edge.
Still an issue on the final test. Works like this:
- Run the template without the custom resource present, but corrected to set generateSecret to false
- Successful build
- UserPool client obviously has the default and unusable URLs
- Manually adjust the URLs in the appClient settings in Cognito, e.g. https://dxxxxxxxx.cloudfront.net/parseauth and https://dxxxxxxxx.cloudfront.net
- Create a user
- Log in as user , prompted for change password, enter changed password
See:
cognito auth URL of the form `https://auth-xxx.auth.us-east-1.amazoncognito.com/error
This doesn't look like it's coming from the lambdas, rather from Cognito
This scenario works in the 1.2 version
Not critical for us now that we have the custom resource but might be worth a look
from cloudfront-authorization-at-edge.
I can't reproduce this error. For me deploying the below template, and after deployment changing the redirect URL's, works fine.
So what is different in your case?
- Check your browser's address bar––Cognito puts error message (like "redirect mismatch") there sometimes.
- Check that your user pool has a value (either false or true) for "AllowAdminCreateUserOnly" (even though it is not a required field according to CloudFormation docs, I've seen it be an issue if not specified)
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
TODO
Parameters:
EnableSPAMode:
Type: String
Description: Set to 'false' to disable SPA-specific features (i.e. when deploying a static site that won't interact with logout/refresh)
Default: "true"
AllowedValues:
- "true"
- "false"
OAuthScopes:
Type: CommaDelimitedList
Description: The OAuth scopes to request the User Pool to add to the access token JWT
Default: "phone, email, profile, openid, aws.cognito.signin.user.admin"
PriceClass:
Type: String
Description: The price class of the CloudFront distribution
Default: PriceClass_100
Conditions:
GenerateClientSecret: !Equals
- EnableSPAMode
- "false"
Resources:
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Ref AWS::StackName
AdminCreateUserConfig:
AllowAdminCreateUserOnly: true
UsernameAttributes:
- email
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
PreventUserExistenceErrors: ENABLED
GenerateSecret: !If
- GenerateClientSecret
- true
- false
AllowedOAuthScopes: !Ref OAuthScopes
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthFlows:
- code
SupportedIdentityProviders:
- COGNITO
CallbackURLs:
# Replace
- https://example.com/will-be-replaced
LogoutURLs:
# Replace
- https://example.com/will-be-replaced
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
Domain: !Sub
- "auth-${StackIdSuffix}"
- StackIdSuffix: !Select
- 2
- !Split
- "/"
- !Ref AWS::StackId
UserPoolId: !Ref UserPool
ApplicationBucket:
Type: AWS::S3::Bucket
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Sub "${ApplicationBucket}-OAI"
S3AccessPolicyForOAI:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: ApplicationBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
CanonicalUser:
Fn::GetAtt: [CloudFrontOriginAccessIdentity, S3CanonicalUserId]
Action: "s3:GetObject"
Resource: !Sub "${ApplicationBucket.Arn}/*"
- Effect: "Allow"
Principal:
CanonicalUser:
Fn::GetAtt: [CloudFrontOriginAccessIdentity, S3CanonicalUserId]
Action: "s3:ListBucket"
Resource: !GetAtt ApplicationBucket.Arn
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
# Aliases: <your alternate domain names, also pass these to the serverless stack below>
# ViewerCertificate: <the config for your HTTPS certificate>
CacheBehaviors:
- PathPattern: /parseauth
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt MyLambdaEdgeProtectedSpaSetup.Outputs.ParseAuthHandler
TargetOriginId: dummy-origin
ViewerProtocolPolicy: redirect-to-https
- PathPattern: /refreshauth
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt MyLambdaEdgeProtectedSpaSetup.Outputs.RefreshAuthHandler
TargetOriginId: dummy-origin
ViewerProtocolPolicy: redirect-to-https
- PathPattern: /signout
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt MyLambdaEdgeProtectedSpaSetup.Outputs.SignOutHandler
TargetOriginId: dummy-origin
ViewerProtocolPolicy: redirect-to-https
DefaultCacheBehavior:
Compress: true
ForwardedValues:
QueryString: true
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt MyLambdaEdgeProtectedSpaSetup.Outputs.CheckAuthHandler
- EventType: origin-response
LambdaFunctionARN: !GetAtt MyLambdaEdgeProtectedSpaSetup.Outputs.HttpHeadersHandler
TargetOriginId: protected-origin
ViewerProtocolPolicy: redirect-to-https
Enabled: true
Origins:
- DomainName: example.org # Dummy origin is used for Lambda@Edge functions, keep this as-is
Id: dummy-origin
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
- DomainName: !Sub "${ApplicationBucket}.s3.amazonaws.com"
Id: protected-origin
S3OriginConfig:
OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
CustomErrorResponses:
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
PriceClass: !Ref PriceClass
DefaultRootObject: index.html
MyLambdaEdgeProtectedSpaSetup:
Type: AWS::Serverless::Application
DependsOn: UserPoolDomain
Properties:
Location:
ApplicationId: arn:aws:serverlessrepo:us-east-1:520945424137:applications/cloudfront-authorization-at-edge
SemanticVersion: 2.0.1
Parameters:
UserPoolArn: !GetAtt UserPool.Arn
UserPoolClientId: !Ref UserPoolClient
EnableSPAMode: !Ref EnableSPAMode
CreateCloudFrontDistribution: false
OAuthScopes: !Join
- ","
- !Ref OAuthScopes
Outputs:
WebsiteUrl:
Description: URL of the CloudFront distribution that serves your SPA from S3
Value: !Sub "https://${CloudFrontDistribution.DomainName}"
from cloudfront-authorization-at-edge.
Thanks, Otto. I've gone back to that install after doing nothing to it over the weekend, and it is now working! Very peculiar that it stabilized without action. Let's pass over this particular rabbit-hole and I'l raise it again if it becomes an issue. Looks like 2.01 is now a going concern ... will work on some of the cookie injection magic it brings.
from cloudfront-authorization-at-edge.
Related Issues (20)
- CloudFormation did not receive a response from your Custom Resource HOT 19
- Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”) HOT 2
- Refresh issue after token expires HOT 8
- On signout Required String parameter 'redirect_uri' is not present HOT 5
- Possible Open Redirect (CWE-601) in sample code HOT 2
- nonce cookies are not expired HOT 1
- [Feature request] Support multiple Cognito user pool clients HOT 4
- custom domain is not redirecting to cognito hosted ui HOT 1
- Getting blocked by CORS policy but unable to figure out the source HOT 5
- Node version bump HOT 7
- Custom IDP with Amplify and Auth at Edge HOT 9
- Fail on delete of the stack HOT 3
- Function must be in an Active state error on deploying the solution HOT 7
- Errors from Lambda when destroiyng the stack HOT 2
- Cognito TAGS HOT 1
- How Do I add User Pool attributes to Cookies? HOT 1
- A potential risk in cloudfront-authorization-at-edge which can be used to upload malicious code. HOT 4
- Having the ability to tune logs HOT 1
- Deployment to eu-west-2 fails with error: Encountered a permissions error performing a tagging operation HOT 4
- Missing User-Agent header in Post request to cognito HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cloudfront-authorization-at-edge.