Token Based Authentication using Credentials in NodeJS

princesingh19

Prince Singh

Posted on December 23, 2023

Token Based Authentication using Credentials in NodeJS

Hello coders,
I am going to talk about the modern way of token-based authentication using NodeJS, so if you are looking for a start or want to build your own then you have come to right place.

In this post I am going to implement the basic token-based authentication system using ExpressJS, PassportJS and other helpful libraries.

So let's start

First run the command
npm init -y
It will create a package.json file for you with all the necessary defaults

Now we need to install the following packages:

  1. ExpressJS
  2. PassportJS
  3. HelmetJS
  4. Nodemon
  5. Passport-JWT
  6. Passport-Local
  7. JsonWebToken
  8. Cookie-Parser

to install these packages run the following command
npm i express helmet jsonwebtoken passport passport-jwt passport-local nodemon cookie-parser

After this we need to make some changes in the package.json file so that we don't have to run the script manually everytime to see the changes. That's exactly where nodemon comes into the picture. It looks for the changes into the specific file and if there is any change it restarts the server.

Let's add the following script into the scripts section in the package.json file

"start": "nodemon server.js"

Now let's move ahead. Following is the file & folder structure we are going to follow to organize our code

Image description

So far so good. We have organized our code. Now it's time to get our hands dirty by writing some code.

We will start with server.js file

const express = require("express");
// create app instance from express
const app = express();

// specify the port number for your app
const PORT = 3000;

// home page route
app.get("/", (req, res) => res.send("Hi"));

app.listen(PORT, (req, res) => {
    console.log(`Connected on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Now if you run npm run start in the terminal then your server will start and run on the port 3000.

Note: We are not using database at this point to save ourselves from additional database setup. We are using a simple array of objects user model.
If you need this with database then comment down. I will create another post with the database setup.

So let's create a user with array of objects in users.js file in the models folder

const users = [
    { username: "Prince", password: "password1" },
    { username: "Kathan", password: "password2" },
    { username: "Srivinay", password: "password3" },
    { username: "Siddharth", password: "password4" },
];
module.exports = users;
Enter fullscreen mode Exit fullscreen mode

Add below html page for login route in the login.html file in public folder

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>NodeJS Authentication</title>
    </head>
    <body>
        <h1>This is the HtML sent by the server</h1>
        <a href="/secret">Show Me!</a>
        <a href="/auth/google">Google Login</a>
        <a href="/auth/logout">Logout</a>
        <!-- <img src="./logo.png" alt="logo img" /> -->
    </body>
</html>

Enter fullscreen mode Exit fullscreen mode

After this we are going to edit our passport.js file to add the passportjs with Token based authentication to our app.

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

const users = require("./model/users");

// function to extract the cookie from the response header
var cookieExtractor = function (req) {
    var token = null;
    if (req && req.cookies) {
        token = req.cookies["secure_token"];
    }
    return token;
};

// function to use the jwt as strategy to authenticate the users
function passportAuth() {
    try {
        passport.use(
            new JwtStrategy(
                {
                    jwtFromRequest: ExtractJwt.fromExtractors([cookieExtractor]),
                    secretOrKey: "my_secret_key",
                },
                function (jwt_payload, done) {
                    const user = users.find((user) => jwt_payload.username === user.username);
                    if (!user) {
                        return done(null, false);
                    }

                    return done(null, user);
                }
            )
        );
    } catch (error) {
        throw error;
    }
}

module.exports = {
    passportAuth,
};

Enter fullscreen mode Exit fullscreen mode

In above code we are first importing the necessary packages then I have create a cookieExtractor function which is used to extract the cookies from the headers and pass the value of the cookies to passport. Please note that we have used cookies specially to save the authentication tokens so that we don't have to include them seperately in each request if which is the case if we save them in the localStorage or sessionStorage. Sure storing the tokens in cookies have some downside and is susceptible to xsrf but it can be avoided by setting the cookies to httponly, secure and maxAge properties.

After that we using the passportjs middleware which runs on every request that is beign made to our server and extract the cookies from header. It then decodes the token extracted from cookie using a secretKey which you have to set in .env file and extracts the user information from tokens. We are verifying here based on the username only for now to keep things simple. If it founds the user then it returns the user's properties or else returns false.

Now let's create the login routes

Add following code to loginRoutes.js file in the routes folder

const express = require("express");
const loginRouter = express.Router();

//importing the getLoginForm and postLoginDetails from controllers/loginController.js file
const { getLoginForm, postLoginDetails } = require("../controllers/loginController");

// router for the GET method of the login form
loginRouter.get("/", getLoginForm);

// route for the POST method of the login form
loginRouter.post("/", postLoginDetails);

module.exports = loginRouter;
Enter fullscreen mode Exit fullscreen mode

After this we are going to edit the loginController.js file in the controllers folder

const path = require("path");
const jwt = require("jsonwebtoken");

const users = require("../model/users");

// function to serve the login page to the user
const getLoginForm = (req, res) => {
    res.sendFile(path.join(__dirname, "..", "public", "login.html"));
};

// function to handle the login request
const postLoginDetails = (req, res) => {
    // extracting the username and password from the body of the request
    const { username, password } = req.body;
    const user = users.find((user) => user.username === username);

    // sending 404 error along with the message if user is not found in the database
    if (!user) {
        return res.status(404).json({ message: "INCORRECT_USERNAME" });
    } else {
        // generating the token and with the information and sending it to the user on the client end
        const token = jwt.sign({ username: username }, "my_secret_key");

        // setting the token in the cookie so that we don't need to include it manually in each request
        res.cookie("secure_token", token, {
            httpOnly: true, // Ensures cookie cannot be accessed by JavaScript
            maxAge: 1000 * 60 * 60, // Expire in 1 hour,
        });

        // returning the response containing the token and the message
        return res.json({
            message: "sucessfully authentiacted",
            token,
        });
    }
};

// function to add the authentication on the selective routes using the jwt strategy
function authenticate(req, res, next) {
    // Use Passport to authenticate the request using the "jwt" strategy
    passport.authenticate("jwt", { session: false }, (err, user) => {
        console.log(user);
        if (err) next(err); // If there's an error, pass it on to the next middleware
        if (!user) {
            // If the user is not authenticated, send a 401 Unauthorized response
            return res.status(401).json({
                message: "Unauthorized access. No token provided.",
            });
        }
        // If the user is authenticated, attach the user object to the request and move on to the next middleware
        req.user = user;
        next();
    })(req, res, next);
}

module.exports = {
    passportAuth,
    authenticate,
};
Enter fullscreen mode Exit fullscreen mode

In above code we are importing the necessary packages. After that we are serving the login.html file which is served to the user after he visits the /login route in the getLoginForm function.

Now moving on we have postLoginDetails function which first extracts the user's username and password from the request, then looks for the that user in the users model. If it founds the user then it creates a token using the users information and then sets that token in the cookie. We have already talked about why we are using cookies approach. And finally send the successful message to the client side. In case if user is not found then also we are sending "INCORRECT_USERNAME" message to the client side.

After that we have added the authenticate function middleware which you can use to implement the authentication on selective routes. It first extracts the JWT tokens with the help of passportjs and looks for user. If user is found then it returns the user or returns the error message.

Now going back to server.js file and adding the passportjs middleware and login routes

Modifying our existing file like this

const express = require("express");
const helmet = require("helmet");
const passport = require("passport");
const cookieParser = require("cookie-parser");

const { passportAuth } = require("./passport");
const loginRouter = require("./routes/loginRouter");

// create app instance from express
const app = express();

// specify the port number for your app
const PORT = 3000;

// using this helmet middleware to set extra attributes on the request headers
app.use(helmet());

// middleware to parse the cookies in the incoming requests
app.use(cookieParser());

// including the passportjs middleware
passportAuth();

app.use(express.urlencoded({ extended: false }));

// initialising the passportjs middleware
app.use(passport.initialize());

// home page route
app.get("/", (req, res) => res.send("Hi"));

// login route
app.use("/login", loginRouter);


// secret page route which requires the user to be authenticated
app.get("/secret", authenticate, (req, res) => {
    res.send("secret value is 19");
});
Enter fullscreen mode Exit fullscreen mode

Here we have made couple of changes. We have added the helmet middleware so set extra necessary headers, cookieParser middleware to parse the cookies from the requests and added the passportjs middleware. After that we are intializing the passportjs middleware. Lastly we are creating the routes for the /login route.

At this point we have made all the heavy lifting.
Now if you go the http://localhost:3000/ route you should see "Hi".

If you go to http://localhost:3000/secret endpoint then you will get the error message saying "Unauthorized access. No token provided". It means our authentication is working.

Now go to http://localhost:3000/login and you will see the two input fields in which first field is for username and last field is for password.

When you enter any username listed in the users model you will get token and successful message. If you enter wrong username then you will get error.

After this if you again go to http://localhost:3000/secret endpoint you will get you secret endpoint's content which in our case is 'Your secret value is 19'.

Woo hoo! You have successfully added the modern token based authentication in your app.

I hope this article gave you the basic knowledge about implementing authentication logic in your app.

There are still many areas of improvement in the code like verifying the password along with username, adding database support , signup process and many more.
So try them on your own and if stuck, comment below, I will create another article along with github repo for you.

Happy Coding!! :)

💖 💪 🙅 🚩
princesingh19
Prince Singh

Posted on December 23, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related