How to automatically login users after Email/Password authentication

I’m currently building a blog sample app, using NextJS, ApolloClient and MongoDB + MongoRealm. The NextJS skeleton was built after the framework’s official page tutorial.
At the moment, new users can signup, by accessing a SignUp form which is routed at ‘pages/signup’. After entering their credentials, they are redirected to the home page. Then, the freshly signed in users have to visit another page(the one associated with ‘pages/login’ root), which contains the login form, which is responsible with their email/password authentication.
Also, I’ve set up Realm to send a confirmation email at the user’s email address. The email contains a link to a customized page from my NextJs app, which will handle their confirmation(users also have to be confirmed, after requesting a sign in)

The workflow should be established with this. However, I want to automatically login a user, after he/she just logged in(so that they won’t need to sign in and also visit the log in page, when creating their accounts).

The problem I’m encountering is that my React component that handles the user confirmation, doesn’t have access to the user instance’s email and password. I need a way to login the user, without having access to his/her credentials.

Below, I will try to explain exactly why this access restriction happens in the first place. Although the entire ‘_app.js’ is wrapped in some custom providers, I’ll try to keep things as simple as possible, so I’ll present only what is needed for this topic.

My signup.js file looks something like this:

import { useForm } from "react-hook-form";
// Used 'useForm' hook to simplify data extraction from the //input form
import { useAuth } from "members";

const SignUpForm = () => {
  const router = useRouter();
  const { handleSubmit, register } = useForm();
  const { signup } = useAuth();

  const signUpAndRedirect = (form) => {
  signup(form.email, form.password);
  router.push("/");
  // after signing up, redirect client back to home
  };

  return (
{/*My form's 'email' and 'password' fields are only accessible in the SignUpForm component*/}
    <div>
      <form onSubmit={handleSubmit(signUpAndRedirect)}>
        ...
      </form>
    </div>
  );
};

export default SignUpForm;

My login.js file is built after the same concept, the only difference being that ‘signUpAndRedirect’ is replaced with
‘authenticateAndRedirect’:

const authenticateAndRedirect = (form) => {
    login(form.email, form.password);
    router.push("/");
  };

And here is my confirm.js file, which is responsible with extracting the token and tokenId from the confirmation URL. This component is normally only rendered when the client receives the email and clicks on the confirmation link(which basically has the form /confirm, where each token is a string and is added into the URL by Realm).

import Link from "next/link";
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useAuth } from "members";

const Confirm = () => {
  const router = useRouter();
  const { confirm, login } = useAuth();
  useEffect(() => {
    const token = router.query.token;
    const tokenId = router.query.tokenId;

    if (token && tokenId) {
      confirm(token, tokenId);
      login(email, password); // !!! I don't have access to these
    }
  }, [router]);
//used useEffect() to assure the confirmation only happens once, after the component was rendered.

  return (
    <div>
      <h2>
        Thank you for confirming your email. Your profile was 	   successfully
        activated.
      </h2>
      <Link href="/">
        <a>Go back to home</a>
      </Link>
    </div>
  );
};

export default Confirm;

And finally, just a quick look into the signup, login and confirm methods that I have access to through my customized providers. I am quite positive that they work correctly:

const client = () => {
  const { app, credentials } = useRealm();
    const [currentUser, setCurrentUser] = useState(app.currentUser || false);
  const [isAuthenticated, setIsAuthenticated] = useState(user ? true : false);

  
  // Login and logout using email/password.

  const login = async (email, password) => {
    try {
      const userCredentials = await credentials(email, password);
      await app.logIn(userCredentials);

      setCurrentUser(app.currentUser);
      setIsAuthenticated(true);
    } catch (e) {
      throw e;
    }
  };

  const logout = async () => {
    try {
      setUser(null);

      // Sign out from Realm and Auth0.
      await app.currentUser?.logOut();
      // Update the user object.
      setCurrentUser(app.currentUser);
      setIsAuthenticated(false);
      setUser(false);
    } catch (e) {
      throw e;
    }
  };

  const signup = async (email, password) => {
    try {
      await app.emailPasswordAuth.registerUser(email, password);
      // await app.emailPasswordAuth.resendConfirmation(email);
    } catch (e) {
      throw e;
    }
  };

  const confirm = async (token, tokenId) => {
    try {
      await app.emailPasswordAuth.confirmUser(token, tokenId);
    } catch (e) {
      throw e;
    }
  };

  return {
    currentUser,
    login,
    logout,
    signup,
    confirm,
  };
};

export default client;

The currentUser will basically represent the Realm.app.currentUser and will be provided to the _app by my providers.

So, the problem is that my Confirm component doesn’t have access to the email and password fields.
I’ve tried to use the useContext hook, to pass data between sibling components, but quickly abandoned this approach, because I don’t want to pass sensitive data throughout my NextJS pages(The only place where I should use the password is during the MongoDB POST request, since it gets encrypted by Realm Web).

Is there any way I could solve this issue? Maybe an enitirely different approach?

Thank you very much in advance! Any help would be very much appreciated!

Hi @Andrei_Daian,

Welcome to MongoDB Community.

Have you tried to implement a similar logic to our Realm web tutorial:

Here a registration flow is followed by a login attempt:

const handleRegistrationAndLogin = async () => {
  const isValidEmailAddress = validator.isEmail(email);
  setError((e) => ({ ...e, password: null }));
  if (isValidEmailAddress) {
    try {
      // Register the user and, if successful, log them in
      await app.emailPasswordAuth.registerUser(email, password);
      return await handleLogin();
    } catch (err) {
      handleAuthenticationError(err, setError);
    }
  } else {
    setError((err) => ({ ...err, email: "Email is invalid." }));
  }
};

Please let me know if you have any additional questions.

Best regards,
Pavel

1 Like

Thank you very much for replying, Pavel! Is this usable with sending email confirmation method, though? Between await app.emailPasswordAuth.registerUser(email, password) and return await handleLogin(); , it seems I have to interfier the confirmation process. I’m a bit confused about why they’re multiple solutions to the same problem, throughout the officail docs. This is what I’ve used for email/password authentication(Should’ve provided it in my original post) https://docs.mongodb.com/realm/web/manage-email-password-users/#std-label-web-manage-email-password-users I think your above solution treats the User Confirmation Method => Automatically confirm users case. Please correct my if I’m wrong, most probably I’m missing something! :slight_smile:

Oh I see, yes the tutorial assumes automatic confirmation,

@Andrei_Daian, why wouldn’t you want for the user to re-login after confirming the email.

The redirect URL could land them on a login page UI eventually? This is a common procedure in most web apps.

Best regards,
Pavel

2 Likes

Yes, indeed, in the end I decided just to redirect newly created users to the login page. No need to over complicate it! Was just wondering if it were any way of authenticating the user in a different way, using the tokens or something. But I’ll move to a JWT + Auth0 authentication, using an external service. The point of this project was firstly, to learn the basic approaches of MongoRealm and how to link it with the front-end application. Thank you for your replies and keep up the good work!