FWIW, this is what I think I’m going to do. I am using auth0 as my identity store.
- Send a post from my backend to create a new auth0 user. At this point the auth0 user.email_verified = false.
- Send a post to trigger a password reset email for the new user.
I customized the password reset email template with an if statement to disguise it as an invitation when user.email_verified = false
{% if user.email_verified == false %}
<h1>Invitation to our awesome app</h1>
<p>Please verify your email address and set your initial password by clicking the following link:</p>
<p><a href="{{ url }}">Confirm my account</a></p>
{% else %}
<h1>Password Change Request</h1>
<p>You have submitted a password change request. </p>
<p>If it wasn't you please disregard this email and make sure you can still login to your account. If it was you, then to <strong>confirm the password change <a href="{{ url }}">click here</a></strong>.</p>
{% endif %}
<p>If you have any issues with your account, please don’t hesitate to contact us at 1-888-AWESOMECO.</p>
<br>
Thanks!
<br>
- Configure a redirect on the Password Reset email template so that when the user clicks the invitation link, they will be prompted to reset their password and then they will be redirected to our app, which will then ask them to login
- I added an auth0 Rule to set email_verified = true on first login/password reset ( it was one of the canned options)
function (user, context, callback) {
const request = require('request');
const userApiUrl = auth0.baseUrl + '/users/';
// This rule is only for Auth0 databases
if (context.connectionStrategy !== 'auth0') {
return callback(null, user, context);
}
if (user.email_verified || !user.last_password_reset) {
return callback(null, user, context);
}
// Set email verified if a user has already updated his/her password
request.patch({
url: userApiUrl + user.user_id,
headers: {
Authorization: 'Bearer ' + auth0.accessToken
},
json: { email_verified: true },
timeout: 5000
},
function(err, response, body) {
// Setting email verified isn't propagated to id_token in this
// authentication cycle so explicitly set it to true given no errors.
context.idToken.email_verified = (!err && response.statusCode === 200);
// Return with success at this point.
return callback(null, user, context);
});
}
- Next time we need to send them a password reset email it will use the “existing user” flavor of the template
- The invitation email pw reset link has a configurable TTL – it defaults to 5 days. So if they don’t accept the invite it will eventually timeout (and we could send them another one if needed)