I am trying to customize the message sent to the user pre-verification by using the custom message trigger, I verified that the data returned from the lambda is valid and the modifications are as I want them to be, however it seems that those changes are not taking any effect as I get the standard verification details to my email
I failed to find any solutions in aws docs, anyone had the same issue ?
package main
import (
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
// Handler will handle our request comming from the API gateway
func Handler(event events.CognitoEventUserPoolsCustomMessage) (events.CognitoEventUserPoolsCustomMessage, error) {
if event.TriggerSource == "CustomMessage_SignUp" {
event.Response.EmailMessage = "Welcome to myapp, please click the following link to verify your email, this is a custom message"
event.Response.EmailMessage = fmt.Sprintf(`Please click the link below to verify your email address. https://apigateway.myapp.com/auth/validate?client_id=%s&user_name=%s&confirmation_code=%s`, event.CallerContext.ClientID, event.UserName, event.Request.CodeParameter)
}
return event, nil
}
func main() {
lambda.Start(Handler)
}
It seems that in the cognito panel under message customizations verification type code needs to be checked (not link), after I did that the trigger does change the message
It works also for verification link, but you have to use the code parameter as {##Verify email##} and not {####} in your User Pool - Message customization settings. And then also use {##Verify email##} keyword in your html template.
Related
Background:
I have a React web app (utilizing aws-amplify) which is connecting to/using an AWS Cognito User Pool for auth.
I am trying to enable MFA and, more specifically, I want my users to have the option to utilize Software Token TOTP MFA (i.e. Google Authenticator, or similar, app).
When I set my User Pool to have MFA required, I am forced to enable SMS MFA and then Software TOTP is optional. In my case, I have TOTP enabled.
In my web app, I have added the necessary component via:
import { SelectMFAType } from 'aws-amplify-react';
# other code
<SelectMFAType authData={user} MFATypes={{ SMS: true, TOTP: true }} />
# other code
If you're unfamiliar with aws-amplify-react and/or SelectMFAType, this component provides a UI element where the user can choose if they prefer to use SMS or Software TOTP as their MFA method. If they choose SMS, their previously-verified phone number is used and everything works.
If the user chooses TOTP, they are shown a QR code to scan in the authenticator app of their choice and they are prompted with an input field to enter a 6-digit number from the authenticator app to verify TOTP.
This is all very standard for anyone who has used a TOTP MFA option on any other website. If the user enters a correct code from the app, their TOTP choice is verified.
In short SelectMFAType is just a shortcut/stand-in to quickly prototype and test without needing to create a custom component.
Problem:
Now, here's the problem and how to reproduce it. (The starting point is a user who has just enabled TOTP.):
User logs out.
User logs in.
If correct username/password, user is prompted for TOTP.
If correct TOTP, user is logged in. This is working perfectly so far, but it won't stay that way.
User logs out.
User logs in.
If correct username/password, user is prompted for SMS MFA and will receive a text message with 6-digit code. This is the unexpected behavior. I expect it to continue requesting TOTP MFA from their authenticator app unless the user changes their preferred method back to SMS.
From this point forward, the user will only ever be asked for SMS MFA.
In between step 4 and step 7, the user's preference was not changed. Absolutely nothing changed in the React app or in the AWS User Pool settings.
Further, if I interrogate the user via the AWS CLI command
aws cognito-idp admin-get-user --user-pool-id ${MY_ID} --username ${MY_USER_NAME}, I can confirm that the user's MFA preference is exactly what I expect:
{
<other irrelevant keys redacted>
"PreferredMfaSetting": "SOFTWARE_TOKEN_MFA",
"UserMFASettingList": [
"SMS_MFA",
"SOFTWARE_TOKEN_MFA"
]
}
The MFA challenge is an API response from AWS Cognito when the user attempts to authenticate and it is either SMS_MFA or SOFTWARE_TOKEN_MFA. In my case, I get a single SOFTWARE_TOKEN_MFA challenge, but then all future challenges revert, against my wishes, to SMS_MFA.
If I repeat the TOTP setup process (delete the entry from authenticator app, re-verify, etc.), I can repeat all of the steps again. By that, I mean the MFA will expect TOTP one time and then again revert back to SMS after that first time.
Can anyone shed light on this situation? Have you experienced it? Is it a known issue/bug in AWS Cognito? Am I doing something wrong? I feel like if this was broken, there would be a pretty big amount of noise being generated, but I can't find anyone else with the same issue.
Things I have tried:
Searched extensively for anyone else with the same issue here, Google, and the AWS forums.
I have posted on the AWS forums, but that has gone nowhere: https://forums.aws.amazon.com/thread.jspa?threadID=324131
Created a custom component to replace SelectMFAType under the theory that there was something wrong with the implementation in aws-amplify-react. I will paste that custom component at the bottom of this question as a reference.
Completely destroyed and re-created the AWS Cognito User Pool. I thought perhaps it was corrupted or otherwise not working. This made no difference.
SetupTOTP.js Component:
import React, { useContext, useEffect, useState } from 'react';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContextText,
DialogTitle,
TextField,
} from '#material-ui/core';
import { Auth } from 'aws-amplify';
import { ToastsStore } from 'react-toasts';
import QRCode from 'qrcode.react';
import { AuthStateContext } from 'Context/auth-context';
const SetupTOTP = React.memo(props => {
const { open, handleClose } = props;
const { username } = useContext(AuthStateContext);
const [user, setUser] = useState(null);
const [qrCode, setQrCode] = useState('');
const [token, setToken] = useState('');
const handleSave = () => {
Auth.verifyTotpToken(user, token)
.then(() => {
Auth.setPreferredMFA(user, 'TOTP').then(() => {
ToastsStore.success('Token Verified Updated');
handleClose();
});
})
.catch(err => {
console.log(err);
ToastsStore.error(err.message);
});
};
useEffect(() => {
Auth.currentAuthenticatedUser().then(user => {
setUser(user);
});
}, []);
useEffect(() => {
if (username && user) {
Auth.setupTOTP(user).then(code => {
setQrCode(
`otpauth://totp/AWSCognito:${username}?secret=${code}&issuer=REDACTED`
);
});
}
}, [username, user]);
return (
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Change Password</DialogTitle>
<DialogContent>
<QRCode value={qrCode} />
<TextField
margin="dense"
id="name"
label="Verify Token"
type="text"
fullWidth
onChange={e => setToken(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleSave} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
);
});
export default SetupTOTP;
I believe this is a bug in Cognito. See this Amplify issue.
I am using Amazon Cognito for user authentication. After the user is registered verification email is sent to his email address. After clicking on the email link he is prompted with this in his browser.
How can I customize this page in order to insert a script that will trigger deep links within the mobile application, and also make the page look bit nicer?
You can do that using Cognito triggers.
You can configure a trigger template to define a message with a link to a page you control.
The assets will be stored at: amplify/backend/auth/<your-resource-name>CustomMessage/assets
The documentation has more details
Cognito allows you to configure your User Pool to send an email to
your users when they attempt to register an account. You can configure
this email to contain a link to Cognito’s Hosted UI where the user’s
account will be marked as confirmed.
This trigger template allows you to define an email message with a
link to a static S3 bucket that you control, where the user’s account
will be confirmed and they can then be redirected to a URL of your
choice (presumably your application). The URL will automatically
contain the username as a query string parameters.
Please note that this trigger template will create an S3 resource. The
files that populate the static site are available for edit in
amplify/backend/auth/CustomMessage/assets. They
consist of:
index.html
spinner.js (controls the spinner that appears on the page while users are awaiting confirmation)
style.css
verify.js (the script which performs the verification request)
I was not able to customize the verification page provided by AWS. I created my own UI on my page, which sent the generated code to cognito for verification. For that I needed to:
trigger custom email upon registration
put custom link to verification in the email using the codes provided for the lambda
process the codes on my page
send the codes and username through aws package
Step 1.
In AWS Cognito User Pool, customize workflow with triggers, choose "Custom Message". The triggerSource for verification that I check for are:
event.triggerSource === 'CustomMessage_SignUp' || event.triggerSource === 'CustomMessage_ResendCode'
You can see other trigger sources for CustomMessage here: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html
Step 2. The lambda provides parameters for verification for my users: event.request.userAttributes.sub and event.request.codeParameter. Using these to I constructed a link to my page like so:
https://mypage.com?user_name=${event.request.userAttributes.sub}&confirmation_code=${event.request.codeParameter}
Step 3. On my page, I check if the url params for user_name and confirmation_code are present, and display a modal which is supposed to inform the user if the verification went correctly or not.
Using a package "amazon-cognito-identity-js" I process the code and user_name. First I create the user pool:
import { CognitoUserPool } from 'amazon-cognito-identity-js';
//Aws-cognito credentials
const poolData = {
UserPoolId: YOUR_USERPOOL_ID,
ClientId: YOUR_CLIENT_ID,
};
export default new CognitoUserPool(poolData);
Then to process the code I create a user instance:
import { CognitoUser } from 'amazon-cognito-identity-js';
import UserPool from 'utils/UserPool';
const getUser = () => {
return new CognitoUser({
Username: user_name.toLowerCase(),
Pool: UserPool,
});
};
// After that you can process the code:
getUser().confirmRegistration(code, false, function (err, result) {
if (err) {
if (
err.message === 'User cannot be confirmed. Current status is CONFIRMED'
) {
// Handle already confirmed error
} else {
// Handle other errors you want
}
}
// Handle successful verification
});
The account is verified and you can guide the user to the login page or any other.
I'm using a lambda function to customize confirmation emails with AWS Cognito. My lambda function seems to work fine and looks like this:
exports.handler = async (event, context, callback) => {
const sampleTemplate = `<html>
<body>
<div>${event.request.codeParameter}</div>
<div>${event.userName}</div>
</body>
</html>`
if (event.triggerSource === "CustomMessage_AdminCreateUser") {
event.response.emailSubject = 'Lets hope this works'
event.response.emailMessage = sampleTemplate
console.log(event.response) // Logs look as expected
}
callback(null, event);
};
The problem is that when the emails arrive the message body is being overwritten by the content in the User Pools > Message Customizations tab. The subject line is working fine, but the email body is being overwritten. For example, the cognito settings look like this:
And the emails look like this:
As you can see, the lambda function worked for setting the email's subject line, but not the actual content. I can't find any setting to turn off that content and it can't be left blank... Any help is much appreciated.
For anyone finding this, I was able to find the answer. When using the CustomMessage_AdminCreateUser event, cognito will silently throw an error if you use event.userName in the template. Instead use event.request.usernameParameter and it will work
If after doing everything, your custom email template doesn't show up then check the following:
Verification type is set to "code" not "link". ( Your Pool > General settings > message customizations ) Ref: https://github.com/amazon-archives/amazon-cognito-identity-js/issues/521#issuecomment-358383440
Email HTML template shouldn't exceed more than 20,000 UTF characters. Ref: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html#aws-lambda-triggers-custom-message-example
Check this tutorial - https://medium.com/#hasnat_33856/customize-your-cognito-verification-emails-with-html-pages-using-lambda-function-e6fff7ebfb94
We MUST include both the "{username}" and "{####}" placeholder for the custom template for CustomMessage_AdminCreateUser to work. We can place this ourselves in the html file or via the event object's values for the keys event.request.usernameParameter and event.request.codeParameter respectively.
In summary, the html file for CustomMessage_AdminCreateUser must include these two values:
event.request.usernameParameter (has value "{username}") and
event.request.codeParameter (has value "{####}")
I've successfully created a lambda function using Go for the pre sign-up trigger of AWS Cognito.
My problem is that I'm not being able to deny/reject the user if a custom field is not valid (based on custom logic).
I'm returning an error as described in AWS Cognito trigger's guide:
return event, fmt.Errorf("Invalid value for field 'custom:myField'")
also I've tried this options:
returning an empty event (nil is not allowed for the event):
var emptyEvent events.CognitoEventUserPoolsPreSignup
return emptyEvent, fmt.Errorf("Invalid value for field 'custom:myField'")
changing ValidationData in the original event:
event.Request.ValidationData = map[string]string{"custom:myField": "Invalid value for field 'custom:myField."}
return event, fmt.Errorf("Invalid value for field 'custom:myField'")
changing UserAttributes in the original event
event.Request.UserAttributes["email"] = ""
return event, fmt.Errorf("Invalid value for field 'custom:myField'")
All those methods are failing, the user is always created in the User Pool.
What should be the correct way to reject the sign-up request using GoLang lambda function?
Looks like something changed on AWS Cognito Lambda triggers, since today I tried the following source code and it worked as expected:
func handler(event events.CognitoEventUserPoolsPreSignup) (events.CognitoEventUserPoolsPreSignup, error) {
fmt.Println(event)
return event, fmt.Errorf("TESTING LAMBDA ERRORS WITH GOLANG")
}
Also, the previous source code that was not working as expected when I posted this question is currently working (with no changes on my side).
I am in the process of authenticating my application users using djangosaml2.
Using NAMEID_FORMAT_TRANSIENT in my SAML_CONFIG dict like this:
'service': {
# My SP SP
'sp': {
'name': 'My local SP',
'name_id_format': NAMEID_FORMAT_TRANSIENT,
'endpoints': { ... }
The user is authenticated, but the user name is received encrypted. This is a requirement from the SP. I see in the documentation for pysaml(https://github.com/onelogin/python-saml) that there is a "security" set of settings and one of the parameters there is
"nameIdEncrypted": False
My question:
How to include the "security" parameters in the SAML_CONFIG dictionary in settings.py?
The following does not seem to work:
'service': {
'sp': { ... }
}
'metadata': {
....
}
'security': {
# The nameID of the <saml:logoutRequest> sent by the SP will be encrypted
"nameIdEncrypted": True,
# Indicates whether the <samlp:AuthnRequest> messages sent by this SP
# will be signed. [Metadata of the SP will offer this info]
"authnRequestsSigned": False
}
Edit: Correcting myself: Working with my SP i have made sure that metadata and certificates are correct and my user can authenticate if security is turned off on the SP side. However, when they correctly activate security for my site I see the following in my logs when the initial SAML request goes over:
INFO 2015-10-13 10:00:43,478 response status_ok 3188 Not successful operation:
Signature required
ERROR 2015-10-13 10:00:43,478 client_base parse_authn_request_response 3188 SAML status error: Signature required from urn:oasis:names:tc:SAML:2.0:status:Requester
Still the problem remains how to pass the security settings in the SAML_CONFIG dictionary. I have tried to put them at root level, or under 'sp' to no avail. Another way could be putting them in a separate json file and have djangosaml2 use that file somehow, but I do not see how to do that either.
Take a look on this specific django-saml-extension(https://github.com/KristianOellegaard/django-saml-service-provider).
But at python-saml you can find a django-demo that show you how handle that settings:
https://github.com/onelogin/python-saml/tree/master/demo-django
You can see how settings are read from the saml folder.
nameIdEncrypted false means that the NameID sent from the SP is not sent encrypted.
If you want to throw an exception if a NameID received is from IdP is not encrypted you may set wantNameIdEncrypted to true.
Please read the documentation:
https://github.com/onelogin/python-saml#settings
And if you find any issue, please report it at github :)
as smartin
mentioned, you are looking at the wrong package documentation.
pySAML2 documentation here.
To answer your question:
"service": {
"sp": {
"authn_assertions_signed": "true",
}
}
You need to set authn_assertions_signed to true/false so the IDP knows this sp preference.