I'm opening this issue to isolate the discussion on the feature request in #382 referenced as
"Provide solution for finishing the registration when the verification link was not followed (opened) before it has expired"
My understanding, as depicted in the issue title is that we're willing provide a solution for the following situation:
As can be seen in user.js#L528 the built-in User.generateVerificationToken() method to generate a verification token does not generate an instance of AccessToken but just a simple random hash. This token is saved in the User instance.
Hence, the built-in User.confirm() method does a simple equality check on the verificationToken in user.js#L550
馃憠 the built-in implementation of the email verification feature does not manage a TTL on the generated token.
Of course developers, when extending the User model can provide his own User.generateVerificationToken() and User.confirm() methods, dealing with a TTL check. And in that case a function to ask for a regeneration of this token makes sense.
But in the current context of LoopBack provided as-is the requested feature has not much meaning, unless we start by adding a TTL feature to email verification tokens.
Please @bajtos, @superkhau, @loay, @raymondfeng comment.
I'd like to invite people involved in the original discussion in #382 to chime in too: @offlinehacker @TFaga @karlmikko @shaunbruno @johnsoftek @lazaridis-com
Provide a solution to finish the user's registration when the email verification token has expired
the built-in implementation of the email verification feature does not manage a TTL on the generated token.
Good catch!
This leaves me a bit confused about what was mine reasoning for proposing this change in https://github.com/strongloop/loopback/issues/382#issuecomment-224954978.
How about the following two scenarios, is it something we should be prepared to handle?
Scenario 2:
Scenario 1 makes perfect sense to me.
And developers can certainly leverage on such a solution to help with scenario 2 as well.
Any scenario assumes a part of custom code to handle interactions with users:
For example for scenario 1:
ATM, the verify() method could be called multiple times, generating an email with a verification token each time. This would be OK to use again this method as-is, but the developer will lack the user instance to call User.prototype.verify(). So as first look, a simple solution consists in just adding a Model instance shortcut reusing the existing code:
User.verify(email, options, (cb))
Although, it raises the following concern about spam:
The developer will have to take care when implementing the overall logic that the access to the verify-again system prevents someone from spamming people by providing any valid email address. This is actually working similar to the recovery form here.
An advanced implementation would modify the login method to allow the user asking again for email verification instead of throwing back 401: this would actually be cleaner as only a valid unverified account could ask for this verify-again email.
Thoughts?
Regarding the need to handle a TTL by default, i suggest waiting for community feedbacks.
ATM, the verify() method could be called multiple times, generating an email with a verification token each time. This would be OK to use again this method as-is, but the developer will lack the user instance to call User.prototype.verify(). So as first look, a simple solution consists in just adding a Model instance shortcut reusing the existing code:
Sounds reasonably to me.
Although, it raises the following concern about spam:
The developer will have to take care when implementing the overall logic that the access to the verify-again system prevents someone from spamming people by providing any valid email address. This is actually working similar to the recovery form here.
I agree, this is a concern we should address. What are the best practices for implementing this? What big players like Google, Facebook, Amazon do?
An advanced implementation would modify the login method to allow the user asking again for email verification instead of throwing back 401: this would actually be cleaner as only a valid unverified account could ask for this verify-again email.
Can login automatically send the verification email when it detects a valid username+password with an email that's not verified yet? The login will still fail with 401, but the response body can include an extra code to indicate that the verification email was sent again.
I think this proposal should fix both scenarios from my https://github.com/strongloop/loopback/issues/3303#issuecomment-288775368 at the same time.
Thoughts?
Can
loginautomatically send the verification email when it detects a valid username+password with an email that's not verified yet? The login will still fail with 401, but the body can include an extra code to indicate that the verification email was sent again.
I was thinking of an option passed in the login method, so that the method can be called first without it, just displaying a message to user, then called with the option to send again the verification email
What are the best practices for implementing this? What big players like Google, Facebook, Amazon do?
Just checked with my gmail account for password reset, it's a multiple screens workflow:
So no CAPTCHA, but a rather elaborated workflow preventing entry level spam automation and i guess some throttling and exclusion systems enforced underneath
This makes me wonder: do we have built-in means to enforce throttling/blocking on routes returning 401 for example? do not allow more than 5x (401) on login in a period of 1 minutes. Block the account for 2 minutes after 5x (401).
@lazaridis-com : sure 馃憤
nevertheless we also have to consider the way previous features were integrated in LB.
i suggest we go on the following way:
User.verify(email, options, (cb)) to ease custom integration of "verify-again" feature in a more advanced workflow. Warn in the docs on the best practices to mitigate spam when using this methodUser.login()to automatically trigger User.prototype.verify() and enable custom workflows (1. login is rejected, 2. a msg is displayed to user, 3. the login is fired again (possibly with login/password stored in cache) with that option to verify the email)User.login() and force call to User.prototype.verify(), to ease further introduction of the feature without redeploying a new client app versionthoughts?
@ebarault Your proposal LGTM. Here is my expectation of the user experience:
When the user clicks on the initial link with an expired token, the application can prompt the user to resend a new link
When the user tries to login without the email being verified, the application can prompt the user to perform email verification.
From the implementation perspective, I would prefer to align the token generation with oAuth2 (maybe in LoopBack next major):
thanks for sharing your POV @raymondfeng
When the user clicks on the initial link with an expired token, the application can prompt the user to resend a new link
The current implementation of the email verification feature does not handle a TTL, it's just a simple random crypto buffer attached to the user instance. I suggest we add this feature when aligning with oAuth2 and detaching the email verification token from the user.
From the implementation perspective, I would prefer to align the token generation with oAuth2 (maybe in LoopBack next major):
agreed 馃憤 . Let's focus on a simpler implementation for now and move to oAuth2 flow with next major release
In the light of the comments above, I think we can limit the scope of this issue to the following change in LoopBack core:
- implement
User.verify(email, options, (cb))to ease custom integration of "verify-again" feature in a more advanced workflow. Warn in the docs on the best practices to mitigate spam when using this method
I am proposing to also:
User.verify method.Thoughts?
Limit the scope of this issue to the following change in LoopBack core:
implement User.verify(email, options, (cb))
馃憤 : options 2 and 3 can anyhow be implemented with a hook on remote method login
Review and extend the documentation to clearly describe when and how to use the new User.verify method.
A PR already exists to improve User.verify() doc: https://github.com/strongloop/loopback.io/issues/321
I'm starting implementing this feature.
To make it a remote method we need to define a default options param, which can be overridden by the developer.
in User.prototype.verify(options, cb) the only property which has currently no default is the verification type. As for the moment only the email verification is supported i propose to set it as default to reduce frictions when using this feature OOTB.
for the record, here are all the possible options (with example values):
var options = {
type: 'email',
mailer: Email model,
to: '[email protected]',
from: '[email protected]'
subject: 'verification email subject',
text: 'verification email text',
headers: {'Mime-Version': '1.0'},
template: 'path/to/template.ejs',
redirect: '/',
verifyHref: '',
host: 'localhost'
protocol: 'http'
port: 3000,
restApiRoot= '/api',
tokenGenerator: function (user, cb) { cb("random-token"); }
};
I see discussion on here that indicates the ability to resend the the verification link via email has been implemented, but I can't seem to find any examples anywhere.
Does anyone have an example that they can share?
hi @jackrvaughan, have you checked the related commit ?
var verifyOptions = {
type: 'email',
from: '[email protected]'
template: 'verify.ejs',
redirect: '/',
generateVerificationToken: function (user, options, cb) {
cb('random-token');
}
};
user.verify(verifyOptions);
note:
/user/:id/verifyUser.getVerifyOptions() exists to ease the building of the verifyOptions object Thanks @ebarault for getting back so quick!
So would you add a remote method, say "resendVerification" and then execute this in that remote method? How would access be granted though if acls restrict unauthenticated users?
In recent projects that I've worked on, developers like to set mobile number as the primary verification attribute instead of email.
Can there be an option to choose a primary verification attribute from mobile and email?
For example, Facebook treats email as the primary verification attribute, but keeps asking the user to verify his mobile number every time he logs in.
So login failure could be just an option set by the application developers. They may or may not choose to set this option.
Most helpful comment
In the light of the comments above, I think we can limit the scope of this issue to the following change in LoopBack core:
I am proposing to also:
User.verifymethod.Thoughts?