Skip to main content

How to build the Perfect Login System

It's been a while since I've been asking the internet this question. While there are many solutions, boilerplates & code snippets available, few explain the reasons behind putting things the way they are.

closeup photo of turned-on blue and white laptop computer
Pic by Philipp Katzenberger
This Login System will be intended for a Website, but if you do get the gist, you can apply the same to other platforms too.

Where do we need a Login System?

Almost any project with a User model. Protecting and segregating User data is of prime importance, and with that, we also need to add validator middlewares for other routes.
Validator Middlewares restrict access to simple routes such as Dashboard, Messaging, or any other service route which you do not want any non-logged in user to access.

What do we need?

This is an important question. Okay, you've specified what the problem is and what do you want. But now you've to realize what you really want. Visualize:
You've built a website. The user has signed up, then the user has logged in. Each time a user does this, the server shall receive a request to validate the email and password, child's play. Your concern is what happens after.
The user wants to go to the dashboard or the profile page (whatever you've chosen for your project), but the backend doesn't know if the user is logged in or not. We need to send something to the server for it to realize that this is a Logged In user. Then the server can fetch their data and return with the required information.

What can I send?

Good question. Before giving something, ask yourself what do you have. As soon as you log in, if the email and password match, the server sends you a 200 response which means that yes, the user is logged in. Right at this moment, the server will send a unique key, provided only to that particular user (although the user doesn't have to know this). This will all happen behind the scenes in the browser code. 
You shall decide what that unique key is. If you're using MongoDB, it could be the _id of the user's document. 

Climax 1:

And when the 200 response from the server accompanies a unique key, we save that unique key to the Local Storage of the browser, which we can accomplish by adding the following in the JS file:

LocalStorage.setItem()

You can google more about this function. Now whenever you have to make a request, get that 'item' from Local Storage and send it along with the request. The server gets the unique key, identifies the user and it can process whatever it wants through it.

Problem:

Yes, the idea seems really easy and flawless. But let's cross-examine the flow with all possible cases. The user logs in, and the userId is forever stored in your browser's Local Storage. This will also keep the user Logged In forever (like the big professional sites). However, there's one corner case:
If someone else copies the unique key from the Local Storage of the logged in user's browser, and pastes it into another PC, they might log in too (in a way hack the user's account).
 In such a case, there's nothing a user can do to change it ever. The hacker can always remain logged in through the account. Because the server has no record of keeping the user's data, we will have to change the unique key when the user asks us to, and seeing that we have used the userId of the record, we will have to delete the entire user data from the DB!

Climax 2 and Final Solution:

We need to have a record of log In with the server for safe logins and also need to change the unique key. Clearly UserId is too precious to be given out in the open. Here are the tips:
  1. For the unique key, send out an encrypted token containing the userId and the time of login.
  2. Since we will also need to decrypt the token, choose a wise method of encoding. Don't worry about it being decrypted by other hackers, since even if they have the userId and time of login, they still won't be able to login.
  3. On the server side, you need to check for a time gap every time you log in. If the user's time of log in and current time is, say 10 hours, then the request must automatically be denied and redirected to the Log In page, basically, log out the user.
  4. In the Change Password option, provide a new token to the real user, with the userId and current Time encoded. (Preferrable encoding option: JWT)

Final Cross Examination:

Okay, let's check how strong is our login system.
If someone does get access to the encrypted token too, they won't be logged in permanently even if the real user doesn't know about it. Remember, the hackers will never know the password of the user since its safe in the DB and there's no route giving that away. If the user does realize their account is hacked by finding out some suspicious activity, they can change the password and the hacker is automatically logged out then.

Note: You can still add an extra check on the user's security, just in case if the user doesn't realize that their account is hacked. In the Database, add another field in the User's document, storing the initial IP and location of the user at the time of sign up. This way, if the IP changes, that is, a new log in from another PC takes place, you can send a mail to the User asking their permission. 

For this, you will need to add a mailing functionality in your login endpoint. Just like all the safe professional websites! 
  
P.S.: I'm sorry I didn't write any code, but this part was mainly a discussion about the Login System. Stay tuned for the boilerplate code of this!  



Comments

Post a Comment

Popular posts from this blog

Namaste JavaScript Quick Notes

Note:  Akshay Saini's Namaste JavaScript is probably the best course for JavaScript developers out there. These are my personal notes that I made while watching the course; they serve more of as an online quick reference for my understanding and revision, and I hope it benefits anyone reading it too! Everything in JS happens inside an Execution Context. Before a JS code is run, memory is allocated and variables are set as undefined   , and functions are set as their exact code in the scope within the Execution Context. The global execution context hosts all the global variables and function definitions. An Execution Context has 2 components: Memory, that stores variables and functions; and Code, that reads and executes the code. Call Stack maintains the order of execution contexts. Since JS is single threaded and asynchronous, at one point of time, only one function is executed which is at the top of the call stack. For each function, an execution context is created before executi

An introduction to APIs

API is an acronym for Application Programming Interface. Let's start with first defining some basic terms: Browser: These are browsers. To visit any website on the internet, you need a browser. Server: Hmm, this is tough. In simple words, server is a computer. Yes, just like the laptop, or PC at your home. The only difference is that it does not have a screen. Of course, there are other differences in technical specifications, but at its core, the server is just, simply, a computer. That's it. So why is it called a server? Because it serves . When you go to a website like google.com , your computer connects to the internet and gets you your search result. But your computer's internet connection has to get that result from somewhere, right? If the google search result is giving you some answers, the answers have to come from somewhere. What is that place? The answer to that some place is: a server. When you click on the search button on google, or hit enter after typing, &q

Review: Nestjs - Finally a scalable way to build APIs

I have been thinking about this for a long time. There HAS to be a defined way to build APIs in a scalable way.  If you have used Node, Express, etc in your side projects, you might have felt that after a point in development, debugging truly becomes a pain. Sure, enterprise-level API codes are great. But a lot of times, these configurations are too much, and probably not even needed in other projects. To be honest, I haven't seen a lot of Open-Source API codes either to make a judgement on how experienced developers build their APIs. Anyway, I came across an amazing framework recently, and I think if you are coding a complex API, this should be your way to go. Nest.js Nest.js is a framework for building efficient, reliable and scalable server-side applications.  You essentially break your APIs into controllers, services, and modules, which allow you to modularize the smallest of functionalities in your endpoints or the API as a whole. Why is modularizing important? As I have talk