To take advantage of a Cognito custom user pool domain a DNS A Alias record is needed for the domain name, which points to a Cloudfront alias target (created by the Cognito user pool domain). There is no way to get at the alias target in the current implementation of AWS::Cognito::UserPoolDomain.
We need to be able to GetAtt UserPoolDomain.AliasTarget - and ideally also the hosted zone id to avoid hard-coding the zone id (Z2FDTNDATAQYW2).
(more information about what is needed here: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html)
Security
Hi. Any news on this issue?
FWIW I just tried this to see if this is supported yet (just in case) and it was not. Received error:
[Error] /Resources/XXXXXXXX/Properties/AliasTarget/Fn::GetAtt: Resource type AWS::Cognito::UserPoolDomain does not support attribute {AliasTarget}
This is preventing me from using Cognito's hosted UI in my templated solution. Cannot set the generated cloudfront alias target to my Route53 template. Would really like a fix for this.
Any updates?
I ran into this recently and worked around it by creating a custom resource to hold the CloudFront Distribution of the UserPoolDomain and used a lambda to do the lookup since the describe-user-pool-domain api does what we want.
Demo stack here: https://gist.github.com/grosscol/3623d2c2affdd3b88ed4538537bb0850
@grosscol Thanks! So do you run this stack to get the alias target and capture that in a shell variable and pass into another stack to create the RecordSet?
@matthewp You could do it like that. Alternatively, you could use Ouput Exports and [Fn::Import](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html) to use the value in other stacks.
I use the gist'd approach it in the same stack because the custom resource, UPDomain, holds the value. The custom resource request object gets it's properties defined by lambda function pointed to by ServiceToken.
I'm using it to get the distribution for a cognito hosted auth page.... which is a bit of a toy example. It does require creating a Route53 record and pointing it to a cloudfront distribution. So in that sense, I expect this to be generally useful. At least it solved an issue I had that required some ugly external scripting or manual intervention.
Below is roughly how I'm using it in a small stack.
# See gist for definition of lambda that does cloudformation lookup
# GetUserPoolClientCFDistribution:
# Type: AWS::Lambda::Function
# Create UserPoolDomain that will need a record pointing at a cloudfront distribution.
AuthSubDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
CustomDomainConfig:
# Cert arn is supplied as parameter to stack
CertificateArn: !Ref DNSCertArn
Domain: auth.example.com
# User pool id is supplied as parameter to stack.
UserPoolId: !If [ UseInternalPool, !Ref InternalUserPool, !Ref ExtUserPoolId ]
# Record set that will have an alias target pointing to the cloudfront distribution.
AuthDomainRecordSet:
Type: AWS::Route53::RecordSet
DependsOn: AuthSubDomain
Properties:
Name: auth.example.com
HostedZoneId: !Ref ZoneId
Comment: Custom auth domain for MICCA
Type: A
AliasTarget:
# Hard coded zone id for cloudformation
HostedZoneId: Z2FDTNDATAQYW2
# The UPDomain custom resource holds the cloudfront dist domain target,
# which was a property defined by the lambda response
DNSName: !GetAtt UPDomain.CloudFrontDistribution
EvaluateTargetHealth: false
UPDomain:
Type: Custom::UserPoolCloudFrontDistribution
Properties:
# This is the labmda that will define the properties for custom object
ServiceToken: !GetAtt GetUserPoolClientCFDistribution.Arn
# This becomes a ResourceProperty of the event passed to lambda
UserPoolDomain: auth.example.com
I use !Sub auth.${MainDomainName} instead of auth.example.com. Substitute your own domain variables scheme as necessary to make it generally useful to you.
I also stumbled across this recently. 😔
Infrastructure as code is all well and good, but if there such blatant breaking point it really eats on the Cloudformation credibility.
I'm actually pretty clueless about how one can introduce a AWS::Cognito::UserPoolDomain resource without the ability to wire it up with Route53 🤷♂️
@x6j8x see the Domain property of the AWS::Cognito::UserPoolDomain resource in the docs. AWS will host the user pool domain on a subdomain of theirs for you if you don't want to use your own hosted zone. You get something like yourdomain.auth.us-east-1.amazoncognito.com.
One failing, such as not making it convenient to get the DNS Name of the Cloudfront Distribution of a Cognito User Pool, doesn't refute the endeavor.
Creating another stack to extract that value and use Fn::ImportValue sounds to me like a massive overkill, that ends preventing us to use a custom domain 👎
+1 for GetAtt of those needed values
@ayozemr Creating a stack for the sole purpose of extract the cloudfront distribution value does seem like mismatch.
The approach illustrated in the gist above is using a lambda for getting the attribute. In practice, this was done in the same stack where the user pool was set up anyway. If one needed the cloudfront distribution value in multiple stacks, the looked up value could be exported from that stack. Using an entirely separate stack is unnecessary.
In short, it's a lamba that's the substitute for the absent GetAtt; not an entire stack. It would be nice to avoid the 100 lines of workaround to maintain if GetAtt could return the value that the api call cognito.describe_user_pool_domain does.
After I initially raised the ticket I did publish a full stack that addresses the problem with a custom resource lambda. Might be of help to people starting out: https://github.com/virtuability/aws-auth
In API Gateway, the attribute DistributionDomainName is available for this. ("The Amazon CloudFront distribution domain name that's mapped to the custom domain name.")
So if/when AWS decides to implement this for UserPoolDomain, it would be nice if the same naming convention was used.
This seems like a very clear miss to me. Please address this feature...it works in every other part of the system.
It would be very nice to have this in CloudFormation. Right now, we're using the yourdomain.auth.us-east-1.amazoncognito.com solution, but I'm concerned that some users will interpret this change in domain as a phishing attempt. Admittedly though, I haven't gotten any reports from users yet.
@velovix You can use a cloudformation stack to use your custom auth domain (e.g. auth.example.com) for Cognito. See the gist in the comment above for a method of creating a custom resource to hold the cloudfront distribution target of the user pool (UPDomain) that can be used as the AliasTarget in a CloudFront Distribution.
AuthDomainRecordSet:
Type: AWS::Route53::RecordSet
# ...
AliasTarget:
# ...
DNSName: !GetAtt UPDomain.CloudFrontDistribution
The tricky bit is getting the domain of the cognito user pool. This issue boils down to CloudFormation providing GetAtt for the UserPool's domain.
I have a ticket in with support on 6/25/2021 to see if this has been added yet or not. Either way I was able to solve this issue with just a few lines of ansible.
- name: Retrieve Cognito User Pool Custom Domain
shell: aws --region {{ aws_region }} cognito-idp describe-user-pool-domain --domain {{ yourCustomDomain }} --query 'DomainDescription.CloudFrontDistribution' --output text
register: custom_domain_alias
- name: Debug stdout
debug:
msg: "{{ custom_domain_alias.stdout }}"
- name: Update Route53 Alias for {{ yourCustomDomain }}
route53:
state: present
zone: "{{ r53.domain }}"
record: "{{ yourCustomDomain }}"
type: CNAME
value: "{{ custom_domain_alias.stdout }}"
overwrite: yes
Most helpful comment
This is preventing me from using Cognito's hosted UI in my templated solution. Cannot set the generated cloudfront alias target to my Route53 template. Would really like a fix for this.