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 typeStringand is required (required: true). Additionally, we setunique: trueto ensure that each user has a unique email address. Thematchproperty uses a regular expression to enforce a valid email format.password: Represents the password of the user. It is of typeStringand 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, bothemailandpasswordare 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 theemailfield. 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
emailandpasswordfrom 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 theregisterview 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
Usermodel with theemailandpassword(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
registerview 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
passwordfield of theUsermodel.
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
Routerinstance usingexpress.Router(). - We define a
GETroute at/registerto render theregisterview. Theregisterview is rendered usingres.render(), and we pass it the title "Register". - We define a
POSTroute at/registerthat calls thecreateUsermethod from theloginControllermodule 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
Routerusingexpress.Router(). This instance is responsible for defining and handling routes. - We define a
GETroute at/registerusingrouter.get("/register", ...)and provide a callback function that executes when this route is accessed. In this case, the callback function renders theregisterview with the title "Register". - We define a
POSTroute at/registerusingrouter.post("/register", ...). When this route is accessed, when a registration form is submitted, it calls thecreateUsermethod from theloginControllermodule.
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