Consistently getting "invalid token data" when trying to reset user password

I’m trying to implement password reset in my iOS app. Upon user request, I call the following (Swift) code in the app:

let realmApp = App(id: REALM_ID)
realmApp.emailPasswordAuth.callResetPasswordFunction(email: "emailEnteredByUser", password: "password", args: [], completion)

My password reset function is the following:

exports = async function({ token, tokenId, username, password }) {
  const ses = context.services.get('AmazonSES').ses("us-east-2");
  
  const link = `https://.../reset_password?lang=en&token=${token}&tokenId=${tokenId}`;

  // send custom email with link using Amazon SES
  const result = await ses.SendTemplatedEmail({...});

  return { status: 'pending' };
};

I don’t use the password sent by the app (which I literally fill with "password") for security reasons, because I have no way to authenticate the user.

The link to my website points to a JS script that looks like this:

// get parameters
const params = new URLSearchParams(window.location.search);
const lang = params.get("lang");
const token = params.get("token");
const tokenId = params.get("tokenId");

const passwordField = document.getElementById("fpassword");

[...]

function confirmButtonWasClicked() {
	const app = new Realm.App({ id: REALM_ID });
	app.emailPasswordAuth.resetPassword(passwordField.value, token, tokenId).then(
		(value) => {
			success();
		},
		(error) => {
			failure();
		}
	);
}

The problem is, I get the following error every single time I try to reset the password:
Request failed (POST https://stitch.mongodb.com/api/client/v2.0/app/REALM_ID/auth/providers/local-userpass/reset): invalid token data (status 400)

I’m using:
RealmSwift v10.7.2
Realm-web@1.2.0 (https://unpkg.com/realm-web@1.2.0/dist/bundle.iife.js)

I think the arguments to the resetPassword function should be token, tokenId and password

resetPassword(token, tokenId, passwordField.value)

Though in my case, even when I’m using the above sequence, I have the same issue which lead me to this thread. I’m using the same flow as the one you have.

My question is, why there is a need to send password to the callResetPasswordFunction when what we are doing is only sending a email via this function and actual ‘reseting the password’ is happening by calling resetPassword function. Perhaps, we are doing something wrong?

@Anuj_Dhingra I wondered the same thing. Perhaps someone from MongoDB can clarify that?

I’m still having this problem and it’s blocking my next release…

I tried manually calling the resetPassword function from a NodeJS script and it didn’t work either.

I tried manually calling the resetPassword function from the client iOS app and it worked.

So I had the hypothesis that the same client app needed to call both callResetPasswordFunction and resetPassword. So I made the following JS script:

const Realm = require("realm");

const appID = "appID";
const app = new Realm.App({ id: appID, timeout: 10000 });

const tokenId = "someTokenID";
const token = "someToken";

app.emailPasswordAuth.callResetPasswordFunction("someUserEmail", "password", []).then(
	(value) => {
		console.log("SUCCESS");
	},
	(error) => {
		console.log(error);
	}
);

// app.emailPasswordAuth.resetPassword("password", token, tokenId).then(
// 	(value) => {
// 		console.log("SUCCESS");
// 	},
// 	(error) => {
// 		console.log(token);
// 		console.log(tokenId);
// 		console.log(error);
// 	}
// );

Basically, I call the script once to send the reset password email, then I copy paste the token and tokenId I receive in the email, switch the commented code in the script, and run it again to reset the password.

It doesn’t work.

This seems like a bug in the NodeJS driver. I’m going to file a bug.

Filed here:

There seems to be a security hole in the ‘realm-web’ SDK with the resetPassword function.

The initial “newPassword” value that you enter in app.emailPasswordAuth.callResetPasswordFunction(email, newPassword) doesn’t matter when you confirm it with app.emailPasswordAuth.resetPassword(token, tokenId, newPassword)

What I really mean is that you can enter any value for password in app.emailPasswordAuth.resetPassword(token, tokenId, newPassword) and the realm-web SDK confirms the password change like the following.

app.emailPasswordAuth.callResetPasswordFunction(email, "123Apple#")
app.emailPasswordAuth.resetPassword(token, token_id, "CrazyBug99")

Running that code through the reset password function will actually confirm the password change when it clearly shouldn’t as “123Apple#” is different from “CrazyBug99” (assuming we extracted the right token and tokenId)