DEV Community

Romeovcn
Romeovcn

Posted on

How to make a JWT cookie work in local

When I was making authentification cookies I couldn't find clear help for both client and server side. So to prevent you from wasting time like me, I'm making this article :

Login

1. Client side request

This fetch request send the information typed by the user to verify if the name and the password are correct and receive a response back which is the JWT cookie.

  const response = await fetch("http://127.0.0.1:8080/user/signin", {
    method: "POST",
    credentials: "include",
    headers: {
      "content-type": "application/json",
    },
    body: JSON.stringify({
      pseudo: pseudo,
      password: password,
    }),
  })
    .then((res) => res.json())
    .then((data) => {
      console.log(data);
    });
Enter fullscreen mode Exit fullscreen mode

I've seen a lot of people say "my cookie is working only on postman but not on my local server". The answer to this problem is the CORS (Cross-origin resource sharing) options.

The important part here is credentials: "include", it allows you to send cookies even if the URL or the port of the request is different from the response one. In opposite to "same-origin" which is the default value.

2. CORS options

But for it to work you also need to set two CORS options :

  app.use(
    cors({
      origin: ["http://127.0.0.1:8080", "http://127.0.0.1:5500"],
      credentials: true,
    })
  );
Enter fullscreen mode Exit fullscreen mode

origin : By default, pages with different URL cannot access to each other. Using origin: ["http://127.0.0.1:8080", "http://127.0.0.1:5500"], will add the two host URL to the Access-Control-Allow-Origin header allowing you to make request between them.

credentials : As i said, by default CORS does not include cookies on cross-origin requests so they can only go to origins from which they came.

3. Controller

We now make our controller that check if the user information is correct, create the JWT token with the user id and create the cookie with the JWT token.

const JWT_MAX_AGE = 1000 * 60 * 60 * 24 * 30; // 30 days in ms

router.post("/login", async (req, res) => {
  if (!req.body.pseudo) return res.status(400).send({ ok: false, error: "Please provide a pseudo" });
  if (!req.body.password) return res.status(400).send({ ok: false, error: "Please provide a password" });
  const user = await UserObject.findOne({ pseudo: req.body.pseudo });
  if (!user) return res.status(400).send({ ok: false, error: "User does not exist" });
  if (req.body.password !== user.password) return res.status(400).send({ ok: false, error: "Authentification is incorrect" });
  // create a JWT token with the user id
  const token = jwt.sign({ _id: user._id }, "your-secret-key", { expiresIn: JWT_MAX_AGE });
  // create a cookie with the jwt token
  res.cookie("jwt", token, { maxAge: JWT_MAX_AGE, httpOnly: true, secure: true });

  return res.status(200).send({ ok: true, token: "JWT " + token });
});
Enter fullscreen mode Exit fullscreen mode

Make sure to store your token secret key in a secure file (.env).

You can set some options to your cookies to make it more secure against XSS attacks for exemple :

httpOnly : Flags the cookie to be accessible only by the web server and not via JavaScript in the browser.

secure : Marks the cookie to be used only by HTTPS protocol and not by HTTP. (except on localhost)

maxAge : option for setting the expiry time relative to the current time in milliseconds.

Display user informations

1. Passport

Once you're logged in, we want to manage route authorization. So we need to get the value of the desired cookie and decrypt the JWT token to get the user id. To do this we will need Passport-JWT strategy, which has also the nice feature to add the DB user in the request object, so that it's available in the controller afterwards.

const passport = require("passport");
const config = require("./config");
const JwtStrategy = require("passport-jwt").Strategy;

// load up the user model
const User = require("./models/user");

const cookieExtractor = function (req) {
  let token = null;
  if (req && req.cookies) token = req.cookies["jwt"];
  return token; 
// return the value of the cookie named jwt
};

module.exports = (app) => {
  passport.use(
    "user",
    new JwtStrategy(
      {
        jwtFromRequest: cookieExtractor, // JWT token value
        secretOrKey: "your-secret-key",
      },
      async function (jwtPayload, done) {
        try {
          const user = await User.findById(jwtPayload._id);
          if (user) return done(null, user);
        } catch (e) {
          console.log("error passport", e);
        }

        return done(null, false);
      }
    )
  );

  app.use(passport.initialize());
};
Enter fullscreen mode Exit fullscreen mode

If the token is properly decrypted and not expired, we will try to get the user from the database, and if a user exists, passport will add this user in the request object.
Otherwise, passport will reject the request by sending something like res.status(401).send({ ok: false, error: 'Unauthorized' })

2. Controller

And the result route to display the user informations

router.get(
  "/result",
  passport.authenticate("user", { session: false }),
  catchErrors(async (req, res) => {
    console.log(req.user, "Identified user");
    res.status(200).send({ ok: true, data: req.user });
  })
);
Enter fullscreen mode Exit fullscreen mode

Logout

1. Client side request

We can now make our Logout route.

  const response = await fetch("http://127.0.0.1:8080/user/logout", {
    method: "GET",
    credentials: "include",
  })
    .then((res) => res.json())
    .then((data) => {
      console.log(data);
    });
Enter fullscreen mode Exit fullscreen mode

This fetch function load our logout route and clear our cookie.

2. Controller

router.get(
  "/logout",
  catchErrors(async (req, res) => {
    // delete the cookie with the name jwt
    res.clearCookie("jwt", {});
    res.status(200).send({ message: "Successfully logged out" });
  })
);
Enter fullscreen mode Exit fullscreen mode

Make sure to secure your CORS options before sending to production. You can easily find good articles about that.

You can find all the files in my github repository

Hope this helped.

Discussion (0)