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 for1h
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 valueBearer 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!.