Authentification in NodeJS with Express using JWT

Github repository

I don’t think it’s necessary to explain why we need to have an authentication system in an application at all. You’ve probably heard of the terms authentication and authorization and I have to point out that these words have different meanings.
“Authentication is the act of validating that users are whom they claim to be. This is the first step in any security process. ” Okta.com
“Authorization in system security is the process of giving the user permission to access a specific resource or function. This term is often used interchangeably with access control or client privilege.” Okta.com

In this tutorial we will learn how to make an authentication system using JWT.

Database models

We will first have to deal with the database because we need to store user data somewhere. We need to store email and hashed password which will be used later for the sign in process. For this tutorial we will use NoSQL MongoDB database and we will also use mongoose. Mongoose is a MongoDB object modeling tool which is designed to work in an asynchronous environment and supports both promises and callbacks.

We will install the necessary packages:

npm install --save mongoose
npm install --save-dev @types/mongoose

Enter fullscreen mode

Exit fullscreen mode

After the packages are installed, we can start making the model. We will create a model for the user who will have the fields _id, email, name and password. We will also create a unique email index so that there are no two users with the same email in our database.

import

{

model

,

Model

,

Schema

}

from

'

mongoose

'

;

export

interface

IUser

{

_id

:

string

;

email

:

string

;

password

:

string

;

name

:

string

;

}

const

IUserSchema

=

new

Schema

<

IUser

>

(

{

_id

:

{

type

:

String

,

required

:

true

},

email

:

{

type

:

String

,

required

:

true

,

lowercase

:

true

,

index

:

true

,

unique

:

true

,

},

name

:

{

type

:

String

,

required

:

true

},

password

:

{

type

:

String

,

required

:

true

},

},

{

collection

:

'

user

'

,

timestamps

:

true

}

);

export

const

UserModel

:

Model

<

IUser

>

=

model

(

'

user

'

,

IUserSchema

);

Enter fullscreen mode

Exit fullscreen mode

Now lets create a connection to the MongoDB database via mongoose.

Note: We need to have a MongoDB database running in order to connect to it. If you use docker you can find the docker-compose.yml file on github which link is provided in this tutorial and just run docker-compose up -d.

import

mongoose

,

{

Connection

}

from

'

mongoose

'

;

let

mongooseConnection

:

Connection

=

null

;

export

async

function

connect

():

Promise

<

void

>

{

try

{

mongoose

.

connection

.

on

(

'

connecting

'

,

()

=>

{

console

.

log

(

`MongoDB: connecting.`

);

});

mongoose

.

connection

.

on

(

'

connected

'

,

()

=>

{

console

.

log

(

'

MongoDB: connected.

'

);

});

mongoose

.

connection

.

on

(

'

disconnecting

'

,

()

=>

{

console

.

log

(

'

MongoDB: disconnecting.

'

);

});

mongoose

.

connection

.

on

(

'

disconnected

'

,

()

=>

{

console

.

log

(

'

MongoDB: disconnected.

'

);

});

if

(

mongoose

.

connection

.

readyState

!==

1

&&

mongoose

.

connection

.

readyState

!==

2

)

{

const

conn

=

await

mongoose

.

connect

(

'

mongodb://localhost:27017/ts-tutorial

'

,

{

// <- replace connection string if necessary

autoIndex

:

true

,

serverSelectionTimeoutMS

:

5000

,

});

mongooseConnection

=

conn

.

connection

;

}

}

catch

(

error

)

{

console

.

log

(

`Error connecting to DB`

,

error

);

}

}

Enter fullscreen mode

Exit fullscreen mode

Now in the server.ts file we can call the method for connecting to the database:

connect

();

Enter fullscreen mode

Exit fullscreen mode

If the application is successfully connected to the database then we should get the messages from log:

MongoDB: connecting.
Application started on port 3000!
MongoDB: connected

Enter fullscreen mode

Exit fullscreen mode

Sign up process

We will first create an endpoint to which we will send data to create a new user. We will add the new route in the server.ts file. Email, name and password fields are required (we will not do the validation of parameters). After that, we must first check if there is an existing user with the same email and only after we determine that the user does not exist, can we proceed further.
The next step is to make a hash of the plain password because the plain password is never stored in the database. So when we create a new user we take his plain password, make a hash and keep the hash in the database. We will need the hashed password later for the sign in process.

Required npm packages:

npm 

install

--save

ulid npm

install

--save

bcrypt npm

install

--save-dev

@types/bcrypt

Enter fullscreen mode

Exit fullscreen mode

app

.

post

(

'

/sign-up

'

,

async

(

req

:

Request

,

res

:

Response

,

next

:

NextFunction

)

=>

{

const

{

email

,

name

,

password

}

=

req

.

body

;

// check if user exists

const

userExists

=

await

UserModel

.

findOne

({

email

:

email

});

if

(

!!

userExists

)

{

next

(

new

ErrorException

(

ErrorCode

.

DuplicateEntityError

,

{

email

}));

}

// generate password hash

const

hash

=

passwordHash

(

password

);

const

newUser

:

IUser

=

{

_id

:

ulid

(),

email

,

name

,

password

:

hash

,

};

const

created

=

await

UserModel

.

create

(

newUser

);

res

.

send

({

done

:

true

});

});

Enter fullscreen mode

Exit fullscreen mode

Note: Add the code in server.ts file before the routes for encoding the data that is being sent to our application from the client side.

const

app

=

express

();

app

.

use

(

express

.

urlencoded

({

extended

:

true

,

})

);

app

.

use

(

express

.

json

());

Enter fullscreen mode

Exit fullscreen mode

We used the bcrypt library to create a hash from a plain password. The code for hashing and comparing plain and hashed passwords:

import

bcrypt

from

'

bcrypt

'

;

export

const

passwordHash

=

(

plainPassword

:

string

):

string

=>

{

const

hash

=

bcrypt

.

hashSync

(

plainPassword

,

10

);

return

hash

;

};

export

const

comparePassword

=

(

plainPassword

:

string

,

passwordHash

:

string

):

boolean

=>

{

const

compared

=

bcrypt

.

compareSync

(

plainPassword

,

passwordHash

);

return

compared

;

};

Enter fullscreen mode

Exit fullscreen mode

In the code above, you can see that we have two functions. The passwordHash function will hash a plain password.
The comparePassword function will check that the plain password entered is the same as the hash from the database. We will need this method later for the login form.

If we have successfully created a user in the database, the next step is to create a JWT when the user tries to sign in.

Sign in process

As we said in the introduction, we will use the jsonwebtoken package and for that we need to install the packages:

npm 

install

--save

jsonwebtoken npm

install

--save-dev

@types/jsonwebtoken

Enter fullscreen mode

Exit fullscreen mode

Actually how does it work? It is necessary to create a route for sign in where it will be necessary to enter email and password.

We will first check if there is a user with the provided email and if there is one, then we will take the password hash that is saved in the database. It is necessary to check whether the plain password from the login form agrees with the hash password from the database using the comparePassword method. If the method returns true then the user has entered a good password, otherwise the method will return false.

After that, it is necessary to generate jsonwebtoken through the mentioned library. We will generate the JWT with the help of a secret key which we keep in our application and the client should not be aware of the secret key. We will generate that jsonwebtoken string and return that token to the client application.

app

.

post

(

'

/sign-in

'

,

async

(

req

:

Request

,

res

:

Response

,

next

:

NextFunction

)

=>

{

const

{

email

,

password

}

=

req

.

body

;

// check if user exists

const

userExists

=

await

UserModel

.

findOne

({

email

:

email

});

if

(

!

userExists

)

{

next

(

new

ErrorException

(

ErrorCode

.

Unauthenticated

));

}

// validate the password

const

validPassword

=

comparePassword

(

password

,

userExists

.

password

);

if

(

!

validPassword

)

{

next

(

new

ErrorException

(

ErrorCode

.

Unauthenticated

));

}

// generate the token

const

token

=

generateAuthToken

(

userExists

);

res

.

send

({

token

});

});

Enter fullscreen mode

Exit fullscreen mode

Code for JWT helper:

import

{

IUser

}

from

'

../models/db/user.db

'

;

import

jwt

from

'

jsonwebtoken

'

;

import

{

ErrorException

}

from

'

../error-handler/error-exception

'

;

import

{

ErrorCode

}

from

'

../error-handler/error-code

'

;

const

jwtKey

=

'

keyyyy

'

;

export

const

generateAuthToken

=

(

user

:

IUser

):

string

=>

{

const

token

=

jwt

.

sign

({

_id

:

user

.

_id

,

email

:

user

.

email

},

jwtKey

,

{

expiresIn

:

'

2h

'

,

});

return

token

;

};

export

const

verifyToken

=

(

token

:

string

):

{

_id

:

string

;

email

:

string

}

=>

{

try

{

const

tokenData

=

jwt

.

verify

(

token

,

jwtKey

);

return

tokenData

as

{

_id

:

string

;

email

:

string

};

}

catch

(

error

)

{

throw

new

ErrorException

(

ErrorCode

.

Unauthenticated

);

}

};

Enter fullscreen mode

Exit fullscreen mode

Authentication middleware

We will create one middleware called authMiddleware which we will put on the routes where we need to have protection and whose job will be to check if the JWT that was generated is valid. authMiddleware function is just a middleware function which will get a token from the header and check its validation. We can check the validation of the token with the function verifyToken which is placed inside our middleware.

The client side is required to send the JWT token string in the header for each API call that requires authentication. Header with authorization token looks like:

Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9TJV...r7E20RMHrHDcEfxjoYZgeFONFh7HgQ

Enter fullscreen mode

Exit fullscreen mode

Protected route with middleware:

app

.

get

(

'

/protected-route

'

,

authMiddleware

,

(

req

:

Request

,

res

:

Response

,

next

:

NextFunction

)

=>

{

// data from the token that is verified

const

tokenData

=

req

.

body

.

tokenData

;

console

.

log

(

'

tokenData

'

,

tokenData

);

res

.

send

(

'

this is a protected route

'

);

});

Enter fullscreen mode

Exit fullscreen mode

The middleware itself:

import

{

Request

,

Response

,

NextFunction

}

from

'

express

'

;

import

{

ErrorCode

}

from

'

../error-handler/error-code

'

;

import

{

ErrorException

}

from

'

../error-handler/error-exception

'

;

import

{

verifyToken

}

from

'

./jwt

'

;

export

const

authMiddleware

=

(

req

:

Request

,

res

:

Response

,

next

:

NextFunction

)

=>

{

const

auth

=

req

.

headers

.

authorization

;

if

(

auth

&&

auth

.

startsWith

(

'

Bearer

'

))

{

const

token

=

auth

.

slice

(

7

);

try

{

const

tokenData

=

verifyToken

(

token

);

req

.

body

.

tokenData

=

tokenData

;

next

();

}

catch

(

error

)

{

throw

new

ErrorException

(

ErrorCode

.

Unauthenticated

);

}

}

else

{

throw

new

ErrorException

(

ErrorCode

.

Unauthenticated

);

}

};

Enter fullscreen mode

Exit fullscreen mode

Wrapping up

In this tutorial we covered how to create basic models with mongoose and MongoDB and how to connect to MongoDB instances. We also learned how to create a new user and save the user in the database and what is important, how to create a hash password using the bcrypt library. After saving the user, we showed how to create a sign in process and generate a token using the jsonwebtoken library. Finally, we demonstrated how to create one middleware to be placed on a route to protect certain routes.