reactjsnode.jsauthenticationoauth-2.0remix.run

Remix run: Authentication succeeds on validation failures


I am trying to validate the login information. If the login fields are empty or have wrong information, I should be able to return the user back to login with an error displayed beneath fields. However, with the current logic the user is still able to succeed to the dash route even when the login fails or when the fields are empty. I am not sure what went wrong? Any pointers?

Code given below:

// Login route action method:
export async function action({ request }: ActionFunctionArgs) {
  try {
    return await authenticator.authenticate("form", request, {
      successRedirect: "/dash",
      failureRedirect: "/login",
    });
  } catch (error) {
    if (error instanceof CustomAuthorizationError) {
      return json({ errors: error.errors, fields: error.fields }, { status: 401 });
    }
    throw error;
  }
}

// Auth.server.ts
const sessionSecret = process.env.SESSION_SECRET;
if (!sessionSecret) {
  throw new Error("SESSION_SECRET is not set.");
}

const authenticator = new Authenticator(sessionStorage);

const formStrategy = new FormStrategy(async ({ form }) => {
  const email = form.get("email") as string;
  const password = form.get("password") as string;

  const userLogin: _Login = {
    email,
    password,
  };

  const validationResult = validate(userLogin);
  console.log("validationResult", validationResult);
  
  if (validationResult.success) {
    const user = await db.user.findUnique({ where: { email } });
    if (!user) {
      throw new AuthorizationError("Invalid email", "", { email: "Please enter a valid email", password: ""});
    }
    const passwordMatch = await bcrypt.compare(
      password,
      user.password as string
    );
    if (!passwordMatch) {
      throw new AuthorizationError("Invalid password", "", { email: "", password: "Please enter valid credentials"});
    }

    if (passwordMatch && email === user.email) {
      return user;
    }
  } else {
    console.error("Invalid data:", validationResult.error.errors);
    const errors = parseErrors<typeof userLogin, LoginError>(validationResult);
    throw new AuthorizationError("Invalid data", errors, { email, password });
  }
});

authenticator.use(formStrategy, "form");

export { authenticator };

Solution

  • They talk about error handling here:

    https://github.com/sergiodxa/remix-auth#errors-handling

    Since your authenticate call uses failureRedirect, it will always redirect back to the login page for any errors thrown in your auth strategy.

    You need to remove failureRedirect and add throwOnError: true.

      try {
        return await authenticator.authenticate('user-pass', request, {
          successRedirect: '/',
          throwOnError: true,
        });
      } catch (error) {
        // Because redirects work by throwing a Response, you need to check if the
        // caught error is a response and return it or throw it again
        if (error instanceof Response) return error;
        if (error instanceof AuthorizationError) {
          // here the error is related to the authentication process
          return json({ error: error.message });
        }
      }
    

    You can see an example here ⚡️ StackBlitz https://stackblitz.com/edit/remix-run-remix-aw3ka1?file=app%2Froutes%2Flogin.tsx