Link to home
Start Free TrialLog in
Avatar of Crazy Horse
Crazy HorseFlag for South Africa

asked on

jwt auth implimentation not redirecting with altered cookie/incorrect token value

I  have a node.js API and a next.js frontend application. The login is working for the most part. But if I open google dev tools and I change the value of the token cookie and try to access the secret page again, it still lets me. Shouldn't it kick me out because the token is no longer the same?

Here is the backend where I check login credentials and then create the token.

exports.login = async (req, res, next) => {
  // check if email exists
  const user = await User.findOne({ email: req.body.email });
  if (!user)
    return res.status(400).send({ errors: ["Invalid login credentials"] });

  //email exists, check password
  const validPass = await bcrypt.compare(req.body.password, user.password);
  if (!validPass)
    return res.status(400).send({ errors: ["Invalid login credentials"] });

  // create jwt token
  const token = jwt.sign(
    { email: user.email, _id: user._id },
    process.env.TOKEN_SECRET,
    { expiresIn: "1h" }
  );
  // add token to header
  res.header("auth-token", token).send(token);
};

Open in new window

Then here is the frontend code for the login where I set the cookie with the token value from the server:

  handleSubmit = async event => {
    const { email, password } = this.state;
    event.preventDefault();
    const user = {
      email,
      password
    };
    // client side validation
    const errors = this.validate(user);
    this.setState({ errors: errors || {} });
    if (errors) return;

    try {
      const response = await login(user);
      const token = response.data;
      cookies.set("token", token);
    } catch (ex) {
      if (ex.response) {
        const errors = ex.response.data.errors;
        this.setState({ errors });
      }
    }
  };

Open in new window

Then I created a route which you can only access if logged in:

Secret.getInitialProps = async ctx => {
  await handleAuthSSR(ctx);
  const response = await axios.get("http://localhost:8000/api/user");

  return {
    users: response.data
  };
};

Open in new window

Here is the auth function (I am using next-cookies)

import nextCookie from "next-cookies";

export const handleAuthSSR = ctx => {
  const { token } = nextCookie(ctx);

  if (ctx.req && !token) {
    ctx.res.writeHead(302, { Location: "/login" });
    ctx.res.end();
    return;
  }
  if (!token) {
    console.log("no token found");
    Router.push("/");
  }

  return token;
};

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Duy Pham
Duy Pham
Flag of Viet Nam image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Crazy Horse

ASKER

Thanks Duy, I just realised this now but still having a bit of trouble. I need to send the token in the header. In the API I check it now:

const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).send("Access denied");

  try {
    const verified = jwt.verify(token, process.env.TOKEN_SECRET);
    req.user = verified;
    next();
  } catch (err) {
    res.status(401).send("Invalid token");
  }
};

Open in new window


My try/catch block on the frontend isn't working though:

Secret.getInitialProps = async ctx => {
  const { token } = nextCookie(ctx);

  try {
    const response = await axios.get("http://localhost:8000/api/user", {
      headers: { Authorization: token }
    });
  } catch (ex) {
    console.log(ex);
  }

  return {
    users: response.data
  };
};

Open in new window


The error I am getting is that response is not defined. If I drop the try/catch then I get the data back but if I test without sending the token in the header then I get a nasty looking page. For now I just want to console.log the error.
I have tried this now. It works but not sure if it's correct.

Secret.getInitialProps = async ctx => {
  const { token } = nextCookie(ctx);
  try {
    const response = await axios.get("http://localhost:8000/api/user", {
      headers: { Authorization: token }
    });
    return {
      users: response.data
    };
  } catch (ex) {
    ctx.res.writeHead(302, { Location: "/login" });
    ctx.res.end();
    return;
  }
};

Open in new window

The last getInitialProps looks fine. You can only return response.data if calling to server succeeds (with valid token),