Adding User Authentication to the Web Application
With the CMS set up next we'll add user authenticatio. The goal is to password protect the CMS and allow only authenticated users to access its CRUDing functionality.
Create the User Model
First, let's create a user model that represents the user document in MongoDB. We'll be using Mongoose for this purpose.
Create a new file called src/models/User.ts
and add the following code:
In the above code, we define the IUser
interface that represents the structure of a user document. It includes the following fields:
email
: Represents the email of the user. It is of typeString
and is required (required: true
). Additionally, we setunique: true
to ensure that each user has a unique email address. Thematch
property uses a regular expression to enforce a valid email format.password
: Represents the password of the user. It is of typeString
and is also required (required: true
).
The userSchema
variable defines the schema using the Mongoose Schema
class. We provide the IUser
interface as a type parameter to ensure that the schema adheres to its structure.
Review the Field Settings
Let's dive deeper into the field settings used in the user schema:
type
: Specifies the data type of the field. In our case, bothemail
andpassword
are of typeString
.required: true
: Ensures that the field is required, meaning it must be present in the user document. If a required field is missing, Mongoose will throw a validation error.unique: true
: Sets a unique constraint on the field. This ensures that no two user documents can have the same value for theemail
field. If a duplicate value is found, Mongoose will throw a duplicate key error.match
: Provides a regular expression pattern that the field's value must match. In our case, we use a regular expression to enforce a valid email format. The pattern/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/
checks for a standard email format.
By setting these field options, we ensure that the email
field is required, unique, and follows a valid email format. Similarly, the password
field is required.
Setting up the controller and the createUser
method
As we did with the CMS we'll create a separate controller file to help manage our application. Create a typescript file of src/conntroller/logincontroller.ts
. This file will contain methods to register and login the user.
Add the following code to the controller:
In the above code, we define a createUser
function that handles the user registration process. Here's a breakdown of the steps involved:
- Extract the
email
andpassword
from the request body usingreq.body
. - Check if a user with the provided email already exists by querying the database using
User.findOne({ email })
. If an existing user is found, we render theregister
view and display an appropriate error message indicating that the user already exists. - Hash the password using
bcrypt.hash(password, 10)
. This asynchronously hashes the password with a cost factor of 10, which determines the computational complexity of the hashing process. - Create a new instance of the
User
model with theemail
andpassword
(hashed) values. - Save the new user to the database using
newUser.save()
. - If all goes well, redirect the user to the login page using
res.redirect('/login')
. - If any errors occur during the process, catch the error, log it, and render the
register
view sending an appropriate error message (which will be set up later).
Hashing with bcrypt
In the createUser
method, we utilize the bcrypt
library to hash the user's password before storing it in the database. Hashing passwords adds an extra layer of security by ensuring that the original password cannot be easily retrieved even if the database is compromised.
Note: You will need to install bcrypt
npm.
Here's how the password hashing process works:
- We use the
bcrypt.hash()
function, which accepts two parameters: the password to be hashed and the salt rounds. - The password is hashed asynchronously, meaning it won't block the execution of other code.
- The second parameter,
10
, represents the number of salt rounds. A higher number increases the time required to hash the password, thereby increasing its security against brute-force attacks. - The hashed password is then stored in the
password
field of theUser
model.
By using bcrypt, we can securely store user passwords in the database.
Setting up the Register/login Routes
Like with the controllers let's keep the routes separate. Setup the login routes with a file of src/routes/loginroutes.ts
.
In the above code, we define the login routes using the Express Router.
- We import the necessary modules:
express
,Request
,Response
, andRouter
. - We create a new
Router
instance usingexpress.Router()
. - We define a
GET
route at/register
to render theregister
view. Theregister
view is rendered usingres.render()
, and we pass it the title "Register". - We define a
POST
route at/register
that calls thecreateUser
method from theloginController
module when a registration form is submitted.
Explaining the Route Setup
The route setup allows us to handle different HTTP requests for specific routes. Let's understand each part of the code:
- We create an instance of the
Router
usingexpress.Router()
. This instance is responsible for defining and handling routes. - We define a
GET
route at/register
usingrouter.get("/register", ...)
and provide a callback function that executes when this route is accessed. In this case, the callback function renders theregister
view with the title "Register". - We define a
POST
route at/register
usingrouter.post("/register", ...)
. When this route is accessed, when a registration form is submitted, it calls thecreateUser
method from theloginController
module.
By separating routes into their own file, we ensure better organization and maintainability in our application.
Registering the Login Routes in the Main Application File
To use the login routes, we need to register them in the entry index.ts
application file.
Import the loginroutes.ts
file by adding the following code:
By using app.use()
, we instruct Express to use the loginRoutes
for requests starting with the root path '/'. This ensures that the routes defined in loginroutes.ts
are accessible in your application.
Building the Register View
Create a EJS view to allow user's to register at src/views/register.ejs
.
You should now be able to register a using by visiting localhost:3000/register
Create a user and check the MongoDb collection to see if a user has been created successfully.
Note: The password is encrypted via Bcrypt. This one way encryption meaning that even with database access we cannot know the password set by the user, although we can check it against values tried at login.
Now we have a user register, we'll build the login next.
Next: User Login