In simple terms Authentication and Authorization on the web means proving/validating the user's identity and level of permissions over the internet.
What will this post cover?
- This post will go over the basics of authentication: What is it? And why is it necessary?
- We will also cover some basic authentication and authorization practices in the Client-Server web architecture.
- And we will be implementing what we cover by building a barebones Express application and securing it with an auth library called Passport JS.
This post will only be an introduction to various authentication and authorization workflows and we will not be dealing with security practices, encryption and hashing.
- It is assumed that you have some experience in web development.
- A basic understanding of the request/response model.
- What is HTTP?
What is Authentication?
In simple terms, authentication means verifying one's identity, and authorization means validating if the identified user has permission to access a resource.
Lets see this in practical terms,
Suppose there is a client "X", here X has requested the server for some resource
(GET /profile.html HTTP/2), an example can be, accessing user profile data.
In this context, authentication will be checking if user X is a valid user and not some randomly generated id.
Once we conclude that X is a valid user, we'll need to verify if X has the permission to access the requested data and that they are not able to access the data that belongs to user Y. This step of checking the users' permission is called authorization.
How can we make sure that the user that sent the HTTP request is a legitimate one?
One simple and widely used method is using user credentials, i.e. username and password.
For a every new user, we direct them to the register route, where they can set their username and password, this will be stored in the database.
Any time the user wants to access a protected resource (lets say... his profile page,
GET /profile.html HTTP/2), we'll need the user to send his credentials with the request.
We can then verify the provided user credentials with the ones we stored in our database for verification, if they match the user is legitimate.
Sounds simple, doable, then can we use this form of authentication in our application?
Before we do that, lets address some problems of this system:
The main issue here is, once we identify the user, how do we keep him identified for subsequent requests. since HTTP is inherently a stateless protocol (It means each HTTP request is its own independent unit, and cannot depend upon or access the pervious requests), we cannot remember the state of the user, i.e. if he was authenticated in the previous request or not.
One way we can solve this is, we can send the user credentials with every HTTP request. But this is really inefficient and insecure:
Firstly, HTTP is a plain text format so any agent that intercepts the communication can see our username and password on every request.
Even if we use HTTPS which is encrypted, cross-checking the user credentials for every request is a waste of computation.
In large scale application were there are huge number of incoming HTTP requests every second this method of authentication can cause performance issues.
Sessions and Cookies
We need to eliminate the need to add user credentials on every HTTP request. In Session based authentication, the server creates a user session when he first logins. The session ID is then sent to users browser in form of a cookies, and while the user logged-in the cookies is attached to every subsequent request made by the browser. Cookie is destroyed when the user logouts.
What is the Session workflow?
When the user first logins, user credentials are passed in the HTTP request. Server verifies these credentials, if they are valid a session object is created.
The session object has a unique id called session ID, the users identification key in the database (generally a primary key like user_id) is also added to it.
After the session object created, we store it in the server, either in the memory or a database (called session store). We only send the session ID of that session to in-form of a cookie to the browser.
The browser then subsequently attaches the cookies on every HTTP request to the server, to authenticate the server only checks if the session id is present in the memory/database and retrieves the user_id from it if needed for any further operations.
when the user logouts, the client side cookie is destroyed as well as the server side session object is also deleted.
For implementing session based authentication we either can manually create session objects for users and handle all the operations.
But it is better to use some session-handling libraries that take care of creating session objects, unique session ids and handling cookie operations, they even have methods for encrypting session/cookie information to prevent tampering, and blacklisting of stolen cookies.
JWT Authentication (JSON web token)
In an oversimplified way, JWTs are like ID Cards/Security Pass for entry. Similar to sessions, a JWT is issued and given to the user when he logins, for every successive request JWT is added to the header by the browser.
But unlike sessions, JWTs do not need any server side storage(like session store) for validation. Once a token is created and sent to the client, it is discarded from the server.
Only the client provided token is enough for authentication this is possible through some complex cryptography.
JWTs use Public key / Private key cryptography, also known as a mathematical trap door, for issuing and validating the tokens.
Refer this -> Explanation for better understanding
A simplified explanation
JWT is generated with private key and verified with a public key, both are stored in the server. The has JWT has 3 parts: header, body and signature.
- Header contains protocol information like encoding and type of token.
- Body contains the payload we want to provide it is generally the user_id(primary key of that user in database) and additional information like roles and permissions.
- The header and the body is hashed using the private key, this hash is added as signature in JWT.
This JWT is sent to the client, which is attached to every subsequent request. Here, if the client tampers with the JWT and modifies its body, the signature will become invalid. The client cannot generate a valid signature as it doesn't have the private key, (private key is kept secret within the server).
When the client sends a request with JWT token in its header, the server reads the token and verifies it with the public key. If it is valid, the client is authenticated, server uses the user_id in the JWT token for any further operations if needed.
It is a best practice to add an iat(issued at time) and an expiry time to the JWT, if the JWT is used beyond the expiry time is automatically in validated.
NodeJS provides a module called jsonwebtokens that can be used to create JWT and express-jwt is used to validate them. For creating and handling public key / private key use bycrypt.
No additional state/database is managed by the server for validating user, only the JWT is enough, this makes JWT stateless.
Because of this there is no extra database queries, which makes JWTs based authentication highly scalable and performant.
The server has no control over JWT once it is issued, so if the JWT is stolen from the client, he can freely use the JWT until it expires.
It is very difficult for the server to INVALIDATE an issued JWT, unlike in sessions where we can just delete the session entry in the database to invalidate that session and force user to re-login.
Top comments (4)
Very well Explained
Very elaborate and informative.
Very well written and explained
Nice & Crisp Blog.