Update user email (Email/password authentication)

How to update the email used by a user in Email/Password authentication?

In my app I have a settings panel which allows the user to update his/her username, email address and password. I am able to change the email address stored in custom user data, but not the one used for authentication.

4 Likes

I am also interested in this. If the user changes his email or simply wants to transfer his account to a new domain, changing the login email is necessary.

1 Like

Hi @Marco_Ancona and @Jean-Baptiste_Beau,

This option is currently cannot be done via email/password Auth in Realm, the only way with this provider is create a new user and associate to user data.

Having said that, you may consider using custom function authentication and store encrypted passwords in the Atlas deployment:
https://docs.mongodb.com/realm/authentication/custom-function

Here you are in full control of the user credentials as long as the process return the same unique identifier

Encryption can be done via util.crypto
https://docs.mongodb.com/realm/functions/utilities#cryptography--utils.crypto-

Thanks
Pavel

1 Like

So we would need to implement our own email/password Auth via function authentication, just to enable a user to change their email address?! This seems to be a very foundational feature for any email authentication system. I expected something like the following to be available out of the box for Realm Auth Email/Password authentication:

firebase.auth()
.signInWithEmailAndPassword('old-email@domain.com', 'super-secret-password')
.then(function(userCredential) {
    userCredential.user.updateEmail('new-email@domain.com')
})

Since this is apparently still not available, I am considering to implement function authentication and am looking for a simple example in the documentation or elsewhere. util.crypto does provide SHA256 which could be used for password hashing.

The example in the documentation just shows a simple username verification without password. Could you point to examples that also include password verification and possibly email verification?

Has anyone in this thread actually implemented something like this using function authentication or would it be preferable to just use JWT authentication with a 3rd party provider like Auth0, which is more fully featured?

Hi @Christian_Wagner ,

I have used auth functions in different ways.

In your case you can store the username and hashed password on this system auth collection allowing just the function of to access it.

Hashing the password can be done in server or client side as you wish the idea is the query do an and between the username and password.

Updating a user is done by just having a flow to update that collection.

You can potentially use username and password regular auth , register the user under a new email and link the old and new identities.

JWT is also a recommended provider and ita up to you if you think to go that path. We just suggest what are the alternatives…

Thanks
Pavel

1 Like

Hello @Pavel_Duchovny and MongoDB team,

I think this shouldn’t be overlooked, to change a user email on the fly would be the best experience for the user.

My understanding is that their Auth ID would retain the same, and we would need to listen to a trigger “on user change email” on our Realm app to change the user data’s email.

Anyway, meanwhile a solution I found is to have sequence of 4 actions:

  • Re-login the user (re-asking their password);
  • Register a new user with the new email and old pass;
  • Run a custom function to change the old email to the new one;
  • Delete the Realm user and logout of the app;

I reckon this is not the best experience because the user will have to login again and click the confirmation email before doing so. Also the confirmation email may well have a message to welcome the user as it were new, which it isn’t.

Here is my personal code in case helps anyone:

// the user data has a "authId" field to attach the Realm User ID to it, also the email too;
// we need a on Login trigger that gets the user data by email and updates it with the new authId;

export async function changeEmail(email, password, newEmail) {
  isLoading.value = true
  let status = 'fail'
  let code = 0
  let error = ''
  email = String(email).toLowerCase()

  try {
    // re-login
    const credentials = Realm.Credentials.emailPassword(email, password)
    const user = await app.logIn(credentials)

    // create new account
    // confirmation email will be sent
    await app.emailPasswordAuth.registerUser(newEmail, password)

    // custom function to change user data to new email
    // the user data will have the new email, and still the old authId
    await user.functions.userChangeEmail(newEmail)
    // note this function must throw an Error if needed so this try block breaks

    // delete old account
    // WARNING if you have a Realm Trigger on user delete make sure it 
    // doesn't mess with the user data. In my case the trigger checks for the
    // user email, so as it was just changed above, the user wouldn't be found, 
    // and the code would exit without changing anything.
    await app.deleteUser(user)

    // clean local user data
    localStorage.clear()
    sessionStorage.clear()
    user.value = false
    userData.value = {}

    // IMPORTANT, this is our current state:
    // - the user is logged out;
    // - they will have to confirm their new email before login in;
    // - the user data is still attached to the old authId;
    // - we do have a login trigger that gets the user data by email and updates it
    // with the new authId, that's basically why this works;

    // at this point it was successful
    // notify user they need to confirm their email and re-login
    status = 'success'

    // SUMMARY
    // This is not the best experience, because the user will have to login again
    // and click the confirmation email before doing so. Also the confirmation email 
    // may well have a message to welcome the user as it were new, which it isn't.
  }
  catch (err) {
    console.log('Failed to change email', err, err.statusCode, err.error, err.errorCode, err.message)

    code = +err.statusCode
    error = err.error
  }

  isLoading.value = false
  return { status, code, error }
}

If that is a bad practice, or if there are improvements to it, please let me know.

1 Like