Cookie Authentication | Session Authentication in Nodejs
Cookie Authentication | Session Authentication in Nodejs

In this post you will learn what is cookie authentication, how does it work in comparison to JWT authentication and how to implement it inside NodeJS.

Diagram

Here I have a nice diagram of cookie authentication.

  • First of all user sends login and password to our backend.
  • Server generates unique cookie for this user and saves it in database
  • Server attaches generated cookie to the request so user automatically gets the cookie in browser
  • Our client should not do anything. With request cookie is set.
  • When our client does next request backend get cookie, parses it and knows what user made a request
  • When we want to log user out we must remove a record with cookie (session) from the database

Cookie authentication vs JWT

Now the question is what is the difference between cookie authentication and JWT authentication? Cookie authentication is older approach and JWT (JSON Web Token) is newer approach.

JWT authentication can be used in any way. Cookie authentication can be used only inside web when we have a browser and cookies.

For example if you build an API and authentication for your mobile application then you can't use cookie authentication. Why? Inside mobile application you don't have a browser and cookies. This means that instead you need to take JWT and implement authentication with custom token which doesn't require cookies.

But as you saw on the diagram cookie authentication is much easier because our client should not do anything at all and we simply attach cookie inside backend and it comes to us again automatically.

Now let's implement cookie authentication on the real example inside Node application.

Initial project

Here I already prepared for us a small express application so we can start building cookie authentication on top of it.

const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const users = [
  {
    id: 1,
    username: "foo",
  },
];

app.use(bodyParser.json());
app.use(cookieParser());

app.get("/private", (req, res) => {
});

app.post("/login", (req, res) => {
  const user = users.find((user) => req.body.username === user.username);

  if (!user || req.body.password !== "123") {
    return res.status(422).json({ error: "Incorrect email or password" });
  }

  res.send(user);
});

app.listen(3001, () => {
  console.log("server started");
});

As you can see it's an express application. On top we create a users array because I don't want to add real database to the mix. Cookie authentication is a layer before the database and it is enough to just get data from the array instead of database.

Here we have a private route which we will implement for logged user later and login route. Inside we try to find a user in the array by username that we provided and check if password equals 123. Then we return a user back.

This is a typical authentication in real project. The only difference is that in real project instead of finding a user in the array we will look for it in the database.

Login request

Here in Postman we did a login request with correct credentials and we got our user back. Obviously if we provide incorrect data we get an error like in real application.

Adding cookie authentication

Now we can start to implement cookie authentication on top of it. And actually for this we must install 2 additional packages.

yarn add cookie-parser uuid

Cookie parser allows our express application to parse cookies. UUID will help us to generate unique strings for our cookies.

Now let's add cookie-parser package to express.

const cookieParser = require("cookie-parser");
...
app.use(cookieParser());

The next thing that I want to create is a storage for our sessions. Typically we store all our sessions in additional table but as we don't use database it is enough to just store them in the object in memory.

const sessions = {};

Now we need to update our login method. We already have code to get a user and check the password. But after this we must generate a unique string which we will use in the cookie header and store in sessions object.

const uuid = require("uuid");
...

app.post("/login", (req, res) => {
  const user = users.find((user) => req.body.username === user.username);

  if (!user || req.body.password !== "123") {
    return res.status(422).json({ error: "Incorrect email or password" });
  }

  const sessionToken = uuid.v4();
  const expiresAt = new Date().setFullYear(new Date().getFullYear() + 1);

  res.send(user);
});

Here we created a unique sessionToken by calling v4 function from uuid. We also created expiresAt property because we want to limit the time how long user is logged in.

If you implement something secure like a bank app it makes sense to have a short session.

If it is something less valuable from security perspective setting it for 1 year is totally fine.

const uuid = require("uuid");
...

app.post("/login", (req, res) => {
  const user = users.find((user) => req.body.username === user.username);

  if (!user || req.body.password !== "123") {
    return res.status(422).json({ error: "Incorrect email or password" });
  }

  const sessionToken = uuid.v4();
  const expiresAt = new Date().setFullYear(new Date().getFullYear() + 1);

  sessions[sessionToken] = {
    expiresAt,
    userId: user.id,
  };

  res.cookie("session_token", sessionToken, { maxAge: expiresAt });

  res.send(user);
});

After we prepared sessionToken and expiresAt we stored them in sessions object by unique sessionToken string. We also set a sessionToken with expiration time in the request inside cookie.

Which means we successfully saved a session on the backend and attached a cookie for the client.

Attached token

As you can see in Postman now after we made a login request we get a cookie with unique ID which was automatically attached to our request. It means that every next request of our client will be authorized.

Checking authentication

Now it is time to protect our /private route.

app.get("/private", (req, res) => {
  const sessionToken = req.cookies["session_token"];

  if (!sessionToken) {
    return res.status(401);
  }

  ...
});

Here we tried to read a session_token from our cookies. If it is not there that we immediately reply with Unauthorized.

app.get("/private", (req, res) => {
  const sessionToken = req.cookies["session_token"];

  if (!sessionToken) {
    return res.status(401);
  }

  const currentUserSession = sessions[sessionToken];

  if (!currentUserSession) {
    return res.status(401);
  }

  if (currentUserSession.expiresAt < new Date()) {
    return res.status(401);
  }

  ...
});

Now we tried to read user session and if it is empty we reploy with Unauthorized. If our session expired we also return 401.

app.get("/private", (req, res) => {
  ...

  console.log("currentUserSession", currentUserSession);

  const currentUser = users.find(
    (user) => user.id === currentUserSession.userId
  );

  res.send(`Hello authorized user ${currentUser.username}`);
});

Now as we completely sure that we have a valid session we try to find a user from our database by userId and we can use this information while generating response for the client.

Private route

This means that we successfully authenticated the user and implemented cookie authentication.

And actually if you are interested to learn how to create a full stack application with Socket IO, Typescript, Node, MongoDB and Angular make sure to check my Build Fullstack Trello clone: WebSocket, Socket IO course.

📚 Source code of what we've done