问题
If a user wants to change to a new email address, I use:
var token = await _userManager.GenerateChangeEmailTokenAsync(user, newEmail);
...and send a confirmation mail which includes this change token. The user clicks the link in the email, arrives back at the site, and now I need to change the email.
BUT, I don't know the new email address.
I've gone through the hassle of an confirmation mail loop, so I want to use it. Asking for the email again risks mistypings that could lock out the user from his account, and is also another hurdle for the user, and more code to maintain for me.
Obvious solutions:
- ask for it again (and risk mistypings, and make the mail loop pointless)
- store the new email address in
User.NewEmailTemp
(yuck, want to keep this stateless) - include it in the link in the email (bad security!)
There may be a better way. The change token includes a bunch of data, including a "purpose" which contains the new email address. Can I somehow extract/decode the purpose, or is it mangled as part of a hashing process?
回答1:
The answer is simply to persist it. Stateless isn't really an option. However, that doesn't mean you need to necessarily pollute the user subdomain with it. You can, for example, create a separate entity like EmailChangeRequest
, with props for the new email, the token, and probably a timestamp (mostly for pruning/expiration). The token, here, wouldn't be for validation, but rather for lookup, so it could be considered a key on the table.
Long and short, when the user clicks the link, you look up the request via the token, get the new email from that, and then call ChangeEmailAsync
with that new email and the token.
If you're really opposed to persistence of any sort, I've toyed around personally with a JWT-esque approach previously. I say "-esque" because passing around a full encrypted and signed JWT as a query or route param is going to make for extremely long URLs for your confirmation links, perhaps even approach request limits. What I did instead, was chop off the header of the JWT, since the header is mostly meant for cross-client communication. In this scenario, I can safely assume an encryption method and issuer and such is unnecessary.
Another change I made was to use TOTP-style tokens instead of the encrypted hashes used by default. This serves to further reduce the JWT token size, and since I'm encrypting the JWT itself, it's not that important if the token inside is encrypted itself. The token provider for things like email confirmation can be easily customized in ConfigureServices
, and there's built-in TOTP token providers that can be used, so this is also relatively low-friction.
I still don't think it's necessarily a bad thing to persist it, especially if you use a separate entity outside of your user subdomain. However, the pseudo JWT approach might fit your needs better if you don't want to go that route.
来源:https://stackoverflow.com/questions/56136114/change-email-workflow-in-asp-net-core