cover

Implementation Authentication in ExpressJS V4.x Using JWT

6 Minutes

Introduction

ExpressJs is a tools in nodejs to create server side application, it’s very popular and have a lot of feature to create web application.

Authentication is the act of proving an assertion, such as the identity of a computer system user - Wikipedia

Authentication is important in private area in our application, it prevent our app from anonymous user to access this area. In other hand, Authorization is the act of specifying right/privileges for accessing resources.

When user login into the system, he will get the token to be used on any action in our private area. Nowdays this token using standard JSON Web Token (JWT) and become Internet Standard, it have expired time, claim information, and encryption. Using this method alone called Stateless Authentication because we don’t have to validate the session. If we validate it, it called Statefull Authentication because we add information session id to the JWT that saved in our application.

Usually we store user information in database system, it’s another story how to integrate them. We had to use database connection and using database function to search and validate user information. We will discuss how to write authentication and authorization to our Endpoint in ExpressJS.

Create Authentication and authorization in ExpressJS

Setup ExpressJs

Create new folder and run npm init into that folder, and then create file index.js.

# Initiate nodejs package
$ npm init

# Create empty file index.js
$ touch index.js

Install library express and dotenv into our project

$ npm i express dotenv

Edit file index.js and fill this code to setup our Endpoint Application

require('dotenv').config()
const express = require('express')

const app = express()
const port = 3000

app.use(express.json())

app.get('/', (req, res) => {
  res.send("Hello world")
})

app.listen(port, () => {
  console.log(`App running on port ${3000}`)
})

Edit file package.json to add Script dev

{
  "name": "express-auth",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "node --watch index.js"
  },
  "dependencies": {
    "express": "~4.16.1"
  }
}

Run the project using command npm run dev. Open your browser and input url localhost:3000, you will see Hello world.

Note: --watch option only stable for nodejs version above 22.x.x. So make sure your nodejs version is compatible with this script.

Create Endpoint Authentication

Before create endpoint authentcation, we have to install package that help us to build secure JWT, we will use Jose

$ npm i jose

Create new file with name auth.js, and and then create function CreateToken to create token using Jose library. We add parameter userInfo that we expect have property email and id. This function have some function that I explain here

  • new jose.SignJWT({ email: userInfo.email }): Create payload email and fill it with user email.
  • jwt.setExpirationTime('1h'): Token will expired for 1h
  • jwt.setSubject(userInfo.id): Subject is who will using this token, so we fill it with user id.
  • jwt.sign(secret): sign the JWT and return as string data.

See more option of jose sign jwt in the Jose documentation

const jose = require('jose')

async function CreateToken(userInfo) {
  const secret = new TextEncoder().encode(process.env.JWT_SECRET)
  const jwt = new jose.SignJWT({ email: userInfo.email })
  jwt.setProtectedHeader({ alg: 'HS256' })
  jwt.setIssuedAt()
  jwt.setIssuer('issuer-your-site.com')
  jwt.setExpirationTime('1h')
  jwt.setSubject(userInfo.id)
  return await jwt.sign(secret)
}

module.exports = {
  CreateToken
}

See there is code process.env.JWT_SECRET in our file, it called env configuration at file .env. So create new file .env and write secret code of JWT, usually this secret is generated randomly.

JWT_SECRET=YOUR-SECRET-JWT-ef2c2bfd0ca4e09e03772c07ae8ed2e6113c10e966f2433e43de1861785ff74a

And then, add new endpoint called /login in our index.js file. We will use simple authentication that only have 1 user in our system, in production environtment should use database instead. Import auth.js into index file, and then call function atuh.CreateToken to generate token. Return the token with key access_token using json format. If user not found, return error instead.

const auth = require('./auth')

....

const User = {
  id: 'user-id-1',
  email: '[email protected]',
  password: 'secret-code-password'
}

app.post('/login', async (req, res) => {
  const body = req.body;
  if (body.email === User.email && body.password === User.password) {
    const token = await auth.CreateToken(User)
    return res.json({
      access_token: token,
    })
  }
  res.status(400).json({
    error: "User not found"
  })
})

Run your application and see in action. I use Curl to test our enpoint, it’s simple curl script using POST method and json data.

$ curl -X POST localhost:3000/login --json '{"email": "[email protected]", "password": "secret-code-password"}'

Your endpoint will return this json data. Verify the token using website jwt.io.

{
  "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImFkbWluQG1haWwuY29tIiwiaWF0IjoxNzI2MDUxNjMxLCJpc3MiOiJpc3N1ZXIteW91ci1zaXRlLmNvbSIsImV4cCI6MTcyNjA1NTIzMSwic3ViIjoidXNlci1pZC0xIn0.5ubrSEKxkH_YeencgxI2bDL6GUOzUvPDr9DaZC2ABeI"
}

Create Authorization

First, create validate token function in our auth.js file

...
async function ValidateToken(token) {
  const secret = new TextEncoder().encode(process.env.JWT_SECRET)

  const { payload } = await jose.jwtVerify(token, secret, {
    issuer: 'issuer-your-site.com',
  })

  if (payload) {
    return payload;
  }
}

module.exports = {
  CreateToken,
  ValidateToken,
}

Validate user token through header of request require middleware to do it. Middleware is a process that run before run function of route. Create new file called middleware.js and then write these code. I explain what we do here :

  • Endpoint will hit using header authorization with value Bearer xxx--token--zz. We will extract token from that.
  • validate the token using auth.ValidateToken function and then add token information to request object.
  • if these function throw error, we catch it inside catch-clause and then show error that token invalid or expired.
  • if all process is valid, we call next() function to call next process of other middleware.
const auth = require('./auth')

async function AutzMiddleware(req, res, next) {
  const header = req.headers['authorization']
  const token = header && header.split(' ')[1]
  if (token == null) return res.status(401).json({ error: "Token not found" })
  try {
    const user = await auth.ValidateToken(token)
    req.user = user
  } catch (err) {
    return res.status(403).json({ error: err.message, message: "Token invalid or expired" })
  }
  next()
}

module.exports = AutzMiddleware

Go back to our index.js file, add new endpoint /private-access for example and then add our middleware in there

const AutzMiddleware = require('./middleware')
...
app.get('/private-access', AutzMiddleware, (req, res) => {
  res.json({
    message: "Private access",
    user: req.user
  });
})

After that, using our previous token to create new request to /private-access, you can use the curl to create request

$ curl localhost:3000/private-access --header "authorization: Bearer YOUR-TOKEN-HERE"

You will get this response if success

{
  "message": "Private access",
  "user": {
    "email": "[email protected]",
    "iat": 1726115416,
    "iss": "issuer-your-site.com",
    "exp": 1726119016,
    "sub": "user-id-1"
  }
}

If validation token is failed, it will shown this error

{
  "error": "signature verification failed",
  "message": "Token invalid or expired"
}

Conclusion

Authentication and authorization is important in building application, but it never ending process, in this article I show how to write authentication and authorization in expressjs version 4.x at basic level but it can became a strong understand how it works before going to other type of tools and framework. We also using middleware in expresjs that help us to validate the request before it proceed to our next process. At this point you can customize the process, improve security, scalability and others. I hope this can help you to build awesome application, reach me if you found this is usefull. Thank you for your time and see you next time!.

comments powered by Disqus