Have inherited an auth0 sign up flow that needs attention for a new client facing application. Users are created internally and sent to Auth0 via the API.
Our full flow looks like so:
A User is created in internal system with an Id, email and phone number
This user is created in Auth0 via the IManagamentAPIClient c# like so managementApi.Users.CreateAsync(clientRequest);
It is created with a default password…. ( not user set )
Once the user is created we then use the IAuthenciationApiClient to send a password reset email like so… This acts as the entry point into our system in the absense of a Register or Welcome email since the user is created in step 2. await authenticationApi.ChangePasswordAsync(new() { Connection = auth0Settings.Value.Connection, Email = user.Email });
The change password ( Link ) email template is used here and we change the wording to make it look somewhat a welcome email ( using some metadata properties set )
This email link has an expiry of a few days meaning if a user clicks on this link in the email after 2 days they are presented with a ‘Expired Link’ dialog.
THE ISSUE is that at this point the user is able to navigate to the normal login screen and select ‘Forgot Password’ and essentially sign up without going through the email link at all. This basically means the expired link in the email is completely pointless and giving us no layer of security other than a dialog saying its expired.
So, a few questions…
How might we tweak our flow so that an expired ‘Sign up’ or ‘Register’ link completely blocks a user from logging in?
Are we creating an anti-pattern creating the users upfront and then sending them a reset password email as the initial email?
Happy to give more information if required but this is as concise I can be as of now.
Indeed, the scenario you have presented considered an anti-pattern and if not handled, this indeed would indicate a security flaw allowing users to bypass the invitation system of your application.
My suggestion in order to prevent the users from resetting the password by themselves would be to deny access to users using a password-reset-post-challenge trigger whenever email_verified is false. Since you are creating the users, this should be by default false. The code for this would look like this:
exports.onExecutePostChallenge = async (event, api) => {
if(event.user.email_verified === false)
{
//Deny access to the user
api.access.deny("Please reset your password through the invitation link");
//Redirect to a custom error page informing the user
api.redirect.sendUserTo("https://my-app.exampleco.com/password_reset_denied");
}
};
This is where the tricky part comes:
Instead of sending a password change email to the users, you will need to send a email verification email to your users
Inside the verification email, you will need to attach an email verification ticket which has the result_url parameter the password change ticket generated beforehand
This way, the users will be blocked to change the password via Universal Login if their account has not been verified and will be forced to reset it through the activation email you will be generating for them.
It can be quite an extended workaround, however, the initial flow you have described would be expected in order to avoid redundancy. Basically, an user who is able to change the password via an email automatically sent to them by the app should be able to ask for a password change email themselves via the Universal Login UI.
Hope the information above is helpful regarding the matter, if you have any other questions, let me know!
Hey @nik.baleca thanks for your reply, really helpful. I’m a little suprised that this flow is not catered for without such workarounds. It all feels like a bit of an afterthought I have to say. Is your suggested flow documented anywhere?
I understand your concept but it gets a bit blurry for me when you suggest that I need to trigger a ‘password change’ ticket once the user is verified? Would this be generated via API integration or a hook in Auth0 itself? If the answer is API, then how do I know the user has been verified to send a ‘password change’ ticket.
Secondly, you mention to attach an ‘email verification ticket’ inside the verification email with a result_url param. Is this just a redirect to a password change once the user has verified themselves? I’m a little confused by the 3rd point as it seems as though you’re suggesting to send a ‘password change’ ticket prior to the verification email and pass the general result_url into the verification email..
Apologies if that is all a bit wordy and thank you again
It all feels like a bit of an afterthought I have to say. Is your suggested flow documented anywhere?
Unfortunately not since the behaviour would be expected. If the user receive a pw reset email or if they sent one themselves via the login page, the outcome or process is fundamentally the same.
I understand your concept but it gets a bit blurry for me when you suggest that I need to trigger a ‘password change’ ticket once the user is verified? Would this be generated via API integration or a hook in Auth0 itself? If the answer is API, then how do I know the user has been verified to send a ‘password change’ ticket.
You would need to first generate a password change and email verification ticket via the Management API as I have linked above. The single link will both verify the user and trigger a password reset for them.
Secondly, you mention to attach an ‘email verification ticket’ inside the verification email with a result_url param. Is this just a redirect to a password change once the user has verified themselves? I’m a little confused by the 3rd point as it seems as though you’re suggesting to send a ‘password change’ ticket prior to the verification email and pass the general result_url into the verification email..
I understand your confusion and I will try to be as clear as possible.
Once the proper URL has been created for the Email Verification Ticket(which will include the Password Change Ticket as a result_url), you will need to send this URL to the user via email. The email template can be anything of your choosing, either a Welcome Email, Verification Email or Password Change Email should be good enough to use. The email itself will contain the verification ticket which upon being clicked, will immediately verify the user’s email and set email_verified to true and they will be redirected to the password change ticket where they will be prompted to set their new password. If the user attempts to reset the password outside of this flow, they will be redirected to a custom error page indicating that they need to set the password through the specific email.
Inside the email that you will be sending, there will not be different links for password change or verification, it will be a single url which both verifies the user and redirects them to the password change screen.
If you need any further clarification or some examples of the flow, please let me know!
Hi @nik.baleca - I have been following this discussion, but am in a tight spot. If I follow your suggestion, and create an action:
exports.onExecutePostChallenge = async (event, api) => {
console.log("[onExecutePostChallenge]: ", event);
if(event.user.email_verified === false) {
//Deny access to the user
api.access.deny("Please reset your password through the invitation link");
//Redirect to a custom error page informing the user
api.redirect.sendUserTo("https://example.com/");
}
};
When user clicks on the link, it leads to invalid link auth0 link screen. Upon checking the logs, it seems to be error of type fcpr, with message Failed to complete password reset as client could not be identified. Any configured Actions will not execute. Please include client_id in the change password request..
Now Im very confused about this error. Im simply using this action, don’t have any other custom code.
Did you generate a password change ticket with a client_id parameter included in the body? This error is not caused by the logic inside your Action, but rather by the existence of an Action in the flow combined with a missing parameter in your Ticket generation.
Also, it is important to mention that in my previous message regarding the PostChallenge action example, I had a complete oversight of the fact that you can either DENY access or REDIRECT the user.
I would suggest to redirect the user straight away since they will not reach the password reset screen anyways or you can customize the error screen inside Universal Login to inform the user of the reason they were denied access.
Got it, can you please tell me how are you testing the Change Password flow for the user? Are you selecting “Forgot Password” under the Universal Login flow of your application or are you sending the email differently (ex: Try button on email template).
As far as I have checked, the specified error would be triggered only when using the “Test” button available under an email template within the Auth0 Dashboard. This happens because the “Test” button does not have a default Client ID and the error is expected. Also, this error can be encountered when trying to generate a change pw ticket without a Client ID passed inside the body of the request.
Otherwise, when testing with the Login Box option or with an /authorize call the behaviour was normal and I could not reproduce the mentioned error.
Can you confirm that this behaviour happens during a normal /authorize call or the Login Box option without triggering the endpoint for the pw change ticket?
Thanks for getting back to me on this. I have been looking into this for a while and I can confirm that this behavior is persistent in both flows. Either if Im testing:
With “Try” button which allows me to trigger emails
With Universal login experience. i.e. recieving the email to reset password by clicking on “forgot password”.
Would highly appreciate if someone can somehow look into our logs?