ASP.NET Identity rollback unverified email change - asp.net

At the moment this is a general question with no code as I am looking for a BEST practices example to my question:
User issues an email change request. (done)
A link is sent to the new address to confirm the new email. (done)
User clicks the confirmation link and the DB update is complete. (done)
What also needs to happen is when the confirmation link is sent for the change, an email should also be sent to the original email address where the user can click a link to reverse the process for whatever reason. I would think also that even if the new email address was accepted, if the original link denies the change it reverts and 2) if the original email reverts and then the new email link is confirmed, that the request would then be denied.
Any direction or code on this matter would be greatly appreciated.

Seems like a simple bit field in the database user record would suffice, or an associated database record would work too. When both emails are sent, mark the field for that user, let's call it "ChangeEmailSent" to 1. When either email is clicked, the field should be updated to 0. The actual changing of the email should only occur if the field is 1.
Some pseudo-code if you like
private void CancelEmailChange(email)
{
var user = Database.GetUser(email);
user.ChangeEmailSent = false;
Database.Save();
}
private void ProcessEmailChange(email)
{
var user = Database.GetUser(email);
if (user.ChangeEmailSent)
{
user.email = getNewEmailAddress(); //whatever logic for a new email
user.ChangeEmailSent = false;
Database.Save();
}
}

Related

Create Firebase Dynamic Link including authentication code to reset password in-app

I'm currently working on a Flutter app where I need the user to sign in. Obviously, there might be cases where the user forgets his password, thus I need to provide a functionality to let the user reset his password.
I use Firebase as backend and sign up and sign in work as well as resetting the password using the default webview provided by Firebase out of the box. However, I'd like to provide the possibility that users of the mobile app are redirected to a custom password reset screen within the app. If I understand this correctly, this is what Dynamic Links are used for - I've also seen that they can be dynamically created from within the app.
Now, obviously, I don't want to simply redirect the user to something like https://www.myapp.com/reset-password, because then I feel that it would be hard to tell which password change belongs to which reset request. So, I thought it might be useful to integrate some kind of authentication code that is contained within the dynamic link, such that the server can identify the user for each password reset.
To accomplish this, I integrated some code that I found on this SO article and modified it a bit to generate a Dynamic Link:
Future<Uri> createDynamicLink({#required String ?mail}) async {
int randomAuthCode = Random().nextInt(1000000);
final DynamicLinkParameters parameters = DynamicLinkParameters(
uriPrefix: "https://myapp.page.link",
link: Uri.parse('https://myapp.page.link/reset-password?authcode=$randomAuthCode'),
androidParameters: AndroidParameters(
packageName: "com.myapp.client.my_app_frontend",
minimumVersion: 1
),
);
final link = await parameters.buildUrl();
final ShortDynamicLink shortenedLink = await DynamicLinkParameters.shortenUrl(
link,
DynamicLinkParametersOptions(shortDynamicLinkPathLength: ShortDynamicLinkPathLength.unguessable)
);
return shortenedLink.shortUrl;
}
However, I don't really get by now how to properly integrate this to send the email based off of this, and also, when to call that function.
The code which is triggered upon requesting the password reset email for an entered email address is the following, although I'm not sure if I need to add actionCodeSettings or not:
void _handleLookupRequest() async {
//some input validators ...
LoadingIndicatorDialog dialog = LoadingIndicatorDialog();
dialog.setContext(context);
dialog.show(context);
final FirebaseAuth auth = FirebaseAuth.instance;
await auth.sendPasswordResetEmail(
email: email,
//actionCodeSettings: //do I need to add these??
).then((user) {
dialog.dismiss();
Navigator.of(context).pop();
})
.catchError((error) {
dialog.dismiss();
String errorType = Errorparser().parseFirebaseAuthErrorType(error);
NotificationDialog().show(
context,
errorType,
Errorparser().parseFirebaseAuthErrorMessage(error)
);
}
);
}
I don't know if I'm perhaps just overengineering this because Firebase already guarantees a safe method to identify the correct user but I only started using Firebase yesterday, so I'm still getting used to all the features. Hopefully someone can help me to implement an in-app password reset like this as I had quite a hard time finding any information on this topic.

Sending Verification email to existing users

I am working on a web app with an existing user base. Email verification was not initially implemented in the sign in flow.
I have successfully added code for sending verification email for all new sign ups but I also wanted to make a small page (or modal) where current users would be shown a button that would send the verification link to their inbox
The current sign up flow where I created the user with createUserWithEmailAndPassword I was able to get access to the user.user.sendEmailVerification method to do so, but cannot find any way to access this method to implement the feature for existing users.
Is there a way to access the sendEmailVerification method after the user has been created?
I am assuming that it would be available within the onAuthStateChange trigger but implementing that would lead to a bad UX (as I do not want to prompt the users everytime they login)
Edit:
I know the documentation states that we can use the firebase.auth().currentUser to get the current user but that, for some reason did not work.
Also, I found references online suggesting to no longer use that method and they mentioned to use the onAuthStateChange method instead, which is why I was looking into that approach
You can try this method:
const btnVerifyEmail = document.getElementById("btn-verify-id")
btnVerifyEmail.onclick = function () {
const user = firebase.auth().currentUser;
user.sendEmailVerification().then(function() {
// Email sent.
console.log("Email Sent")
}).catch(function(error) {
// An error happened.
console.log(error)
});
}
It's mentioned in the documentation right here
The sendEmailVerification() should not be called in the onAuthStateChanged event because it would blast out an email on every page load if the user's email isn't verified.
You should instead display a notification on the page if User.emailVerified is false that contains a link to send the user an email.
Here's a working example:
// On page load watch for auth state changes
firebase.auth().onAuthStateChanged(function(user) {
// If the user is logged in
if (user) {
// If the user's email isn't verified
if (!user.emailVerified) {
// Show the notification bar that informs the user that they need to validate
// their email by clicking a link. Let's pretend the link looks like this:
// Send me a verification email
showNotification();
}
}
});
// Function attached to your link's onclick event
function sendEmailVerification() {
// Retrieve the current user
const user = firebase.auth().currentUser;
// If user's email is already verified, exit
if (user.emailVerified) {
return;
}
// Tell Firebase to send the verification email and discard the promise
user.sendEmailVerification().then().catch();
}
Dharmaraj's answer is good but this is a full example.

meteor-shopify User Creation/ Login after Auth callback

Assuming I want to create users upon authorizing the app, how would I grab their email during the onAuth callback...? Looks like the callback assumes the user is already logged in. Am I thinking about it correctly?
I noticed when installing the Fishbowl Prizes app, after auth I can click on the accounts tab and see that all my account info is pre-populated from my shopify store account (name, email, address, etc).
I'm not sure if I should go by the title or the content of the post in terms of answering your question, so I'll provide a very simple example of how to get the info from the API and do something with it here.
I have provided a more in depth answer related specifically to grabbing the details from the API for user account creation here: https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630
Looks like the callback assumes the user is already logged in.
The userId param is undefined if there is no user. If your onAuth operations don't need to do anything with the user, you can just leave it out of the params. In your case you'll just want to handle it conditionally using an if/else block:
if(!userId){
// do stuff
} else {
// do other stuff
}
On to the example of grabbing those details from the API:
All the prepopulated information you are seeing is available from the Shopify API in the shop object. You already have the access token when onAuth callbacks are fired, so you can just grab it from the API immediately after you have inserted the shop's Keyset.
For the sake of simplicity, in this example we'll assume the user already exists and is logged in. In your server-side onAuth callback (after you have inserted the keyset) you can do something like this to add those fields to the user's profile object:
Shopify.onAuth(function(access_token, authConfig, userId) {
var shopUUID = uuid.new(); // Not secure to name keyset same as the shop!
Shopify.addKeyset(shopUUID, {
access_token: access_token
});
var api = new Shopify.API({
shop: authConfig.shop,
keyset: shopUUID
});
// get the Shop object from the API
var shopObj = api.getShop();
var userInfo = {
'profile.name': shopObj.shop_owner,
'profile.email': shopObj.email,
'profile.phone': shopObj.phone,
'profile.shopName': shopObj.name
};
Meteor.users.update({_id: userId}, {$set: userInfo})
});
Then you can use them in templates like this:
{{currentUser.profile.name}} or {{currentUser.profile.email}}
Or in functions like so:
var realName = Meteor.user().profile.name
or
var userEmail = Meteor.user().profile.email etc
For a more about using this data for user creation, see my explanation here:
https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630

Create chat group in SignalR

How can I create a chat group in SignalR? I tried to find some examples and they weren't helpful. Any help would be appreciated. Here is what I've come up with so far:
public void CreateGroup(string currentUserId, string toConnectTo)
{
string strGroupName = GetUniqueGroupName(currentUserId, toConnectTo);
string connectionId_To = OnlineUser.userObj.Where(item => item.userId == toConnectTo).Select(item => item.connectionId).SingleOrDefault();
if (!string.IsNullOrEmpty(connectionId_To))
{
Groups.Add(Context.ConnectionId, strGroupName);
Groups.Add(connectionId_To, strGroupName);
Clients.Caller.setChatWindow(strGroupName, toConnectTo);
}
}
It's not such easy to create a user friendly chat group that I'm currently working on.
I use sql database, user can register, login, logout, close browser or phone app without logout.
user can create own group, add members to group, delete member from group.
when user send message, call signalr Server method and store message in sql, then Server send message to all users in the same group. if some users are offline, they read message from sql when online again.
the input for the message can be a 'contenteditable div' so that user can add images and formatted text to the message.
something like that!

User verification on the reset password page

I am writing a password-reset page for my website. Here's my idea:
a. User click the "forgot password" link on the login page
b. Redirect to my password-reset page
c. User enter his email address
d. A email message sent to the email address with the link to reset his/her password. The link has security code like ?code="xxxx" in it.
e. User open the link and enter new password, and then click the submit button.
f. My page change user's password.
My question is for step f. In step e, when user opened the link, I could verify his security code and then show the 'new password' and the 'confirm password' fields to user. But when the user clicked the submit button, how could I know this is a real request submited by the user instead of a hacker? Maybe I am wrong, but I think hacker can easily simulate such field data, since there is no validation fields.
There are some idea I can think of to validate the request in step f, but I don't know whether they are right.
1. Add a encrypted cookie in step e and check it in step f?
2. Use a session variable in step e and check it in step f?
3. Add a hidden field in step e and check it in step f?
Are those approaches ok? Which one is better, or is there any better one?
Thanks in advance.
A user entering their username and reset code should log them into the site just as their username and password would. The difference is you then immediately force them to change their password. With this password reset method you're implicitly trusting that the user is the owner of the email account where the code was sent.
Edit:
Ok, so I don't know the first thing about ASP.net.
However, I've handled this problem many times before. Here is a solution of mine in PHP:
<?php
class AuthController extends Zend_Controller_Action
{
public function identifyAction()
{
if ($this->_request->isPost()) {
$username = $this->_getParam('username');
$password = $this->_getParam('password');
if (empty($username) || empty($password)) {
$this->_flashError('Username or password cannot be blank.');
} else {
$user = new User();
$result = $user->login($username, $password);
if ($result->isValid()) {
$user->fromArray((array) $this->_auth->getIdentity());
if ($this->_getParam('changepass') || $user->is_password_expired) {
$this->_redirect('auth/change-password');
return;
}
$this->_doRedirect($user);
return;
} else {
$this->_doFailure($result->getIdentity());
}
}
}
$this->_redirect('/');
}
public function forgotPasswordAction()
{
if ($this->_request->isPost()) {
// Pseudo-random uppercase 6 digit hex value
$resetCode = strtoupper(substr(sha1(uniqid(rand(),true)),0,6));
Doctrine_Query::create()
->update('dUser u')
->set('u.reset_code', '?', array($resetCode))
->where('u.username = ?', array($this->_getParam('username')))
->execute();
$mail = new Zend_Mail();
$mail->setBodyText($this->_resetEmailBody($this->_getParam('username'), $resetCode));
$mail->setFrom('no-reply#example.com', 'Example');
$mail->addTo($this->_getParam('username'));
$mail->setSubject('Forgotten Password Request');
$mail->send();
$this->_flashNotice("Password reset request received.");
$this->_flashNotice("An email with further instructions, including your <em>Reset Code</em>, has been sent to {$this->_getParam('username')}.");
$this->_redirect("auth/reset-password/username/{$this->_getParam('username')}");
}
}
public function resetPasswordAction()
{
$this->view->username = $this->_getParam('username');
$this->view->reset_code = $this->_getParam('reset_code');
if ($this->_request->isPost()) {
$formData = $this->_request->getParams();
if (empty($formData['username']) || empty($formData['reset_code'])) {
$this->_flashError('Username or reset code cannot be blank.');
$this->_redirect('auth/reset-password');
} elseif ($formData['new_password'] !== $formData['confirm_password']) {
$this->_flashError('Password and confirmation do not match.');
$this->_redirect('auth/reset-password');
} else {
$user = new User();
$result = $user->loginWithResetCode($formData['username'], $formData['reset_code']);
if ($result->isValid()) {
$user->updatePassword($result->getIdentity(), $formData['new_password']);
$user->fromArray((array) $this->_auth->getIdentity());
$this->_setLegacySessionData($user);
$this->_flashNotice('Password updated successfully!');
$this->_doRedirect($user);
} else {
$this->_doFailure($result->getIdentity());
$this->_redirect('auth/reset-password');
}
}
}
}
protected function _doFailure($username)
{
$user = Query::create()
->from('User u')
->select('u.is_locked')
->where('u.username = ?', array($username))
->fetchOne();
if ($user->is_locked) {
$lockedMessage = Config::get('auth.lock_message');
if (!$lockedMessage) {
$lockedMessage = 'This account has been locked.';
}
$this->_flashError($lockedMessage);
} else {
$this->_flashError('Invalid username or password');
}
}
}
If you can follow this, it should give you a good idea of what to do. I'll try to summarize:
identifyAction
This is the regular "login" using username and password. It logs the user in and stores their identity in the session.
forgotPasswordAction
This presents the user with a form requesting their username. After entering their username a reset code is generated, stored in their entry in the user table, and they are emailed as well as redirected to the reset password page. This page is unauthenticated, the user is not logged in.
resetPasswordAction
This is where the user is presented with the "resetPassword" form. They must provide their username and the reset code they received via email. This authenticates the user with the given username and reset code, just as if the reset code were a password. If the credentials are valid the user is then redirected to the changePassword action where they are permitted to change their password. The changePasswordAction (not shown) requires the user be authenticated (logged in) either via username/password or username/resetCode
Hope this helps.
If your code that you're emailing is a GUID or some such ID, there is a statistically low chance that someone can guess that code. If you additionally had the link include a hashed version of their email or some other way of linking the code to the user, I think you'd be pretty well safe from malicious input.
I'd be more worried about people being spammed from step c/d, unless you're doing some sort of verification of the email existing currently in your database.

Resources