This was originally posted in here
You want to let only selected users to use certain features of your product? You need proper authentication and authorization process. These two are different. Authentication is the process of identifying if the user is valid. And authorization is the process which checks and grants access to different features or resources.
Both REST and GraphQL are not opinionated. So, you can put in place authentication anyway you like. But, as it is a very sensitive feature, you should always keep security in mind.
In this article, I will be talking about the authentication and authorization process of web applications which are built on top of REST or GraphQL APIs. I will be pointing out some best practices you should follow.
Table of Contents:
- Always use HTTPS
- Saving Hashed Password
- Never Pass the Hash with the Request
- Session or Token
- JWT for Token
- Cookies or Session or Local Storages?
- CSRF Handling
- Short Description
Always use HTTPS
HTTP and HTTPS are both protocols for networks. HTTP means Hypertext Transfer Protocol and HTTPS means Hypertext Transfer Secure.
We can even tell by the name that HTTPS is more secure. So, any website working with user data, credentials, etc. must use HTTPS protocol for transferring data.
How is HTTPS secure? Well, in this protocol, data is first encrypted and then sent to the server. Wondering how this encryption is going to help you?
Assume you want to use a web service where you have to sign in using your username and password. If that website is using HTTP protocol, then any outsider can sniff packets which are being sent to your server including your password in plain text. Payload (username, password, etc.) is encrypted in websites using HTTPS. So, if the attacker wants to know the password, he will have to know the private key of your SSL certificate.
So, HTTPS is a must if you are building any website which will be working with user data. Also, redirect to HTTPS if anyone types HTTP to enter your website.
Saving Hashed Password
Imagine you have the best talents around the world at your disposal. But, if you are saving user passwords in plain text, you are putting your users at risk. Hackers attacked and stole passwords, credit cards, etc. data of millions of users by attacking popular sites like Sony, Adobe, Yahoo, etc. So, even if you have the best engineers in the world, you need to have the basics right. Please do not save the passwords in plain text.
Now you might wonder, if I do not save password, how can I know if one who is trying to sign in, is the correct user?
The answer is:
Use Hash
Anyway, do not get confused with "Hashtag". ;)
Hash is a function which transforms one data into another in a very fast manner. Hashed data is not reversible. So, hash the password and save it in the database. Next time someone tries to sign in, hash password provided by him/her and check against the database.
Now we have secured everything. Right?
No. Attackers can use rainbow tables to reverse the hashed data to plain text.
Now what?
Well, you should add salt to your hashing process. Beware, salt is not the silver bullet. Imagine your servers have been attacked and attackers have got access to your database. They will probably find your salt and hashing algorithm in no time. Then they will use brute force to generate a rainbow table with your salt and hashing algorithm to find out user passwords.
So, here we cannot do much. Popular hashing algorithms are very fast. We need to slow attackers down. So we should use bycrypt as our hashing algorithm. It is very slow. But, in this case, we want to slow down the process. A user won't care much if the hashing algorithm needed .2-.3 seconds extra while signing in. But, it will make attackers life hell.
Never Pass the Hash with the Request
Now you might be thinking, how would I know if the user is correct in every REST or GraphQL request?
First of all, never send the hash of password back from the server. It's the number one rule.
If you are passing around the hash, it will eventually end up in wrong hands.
So, what should we do? What are the best practices for letting the server know if the user is valid or not?
Instead of passing hash of password to front end to use that in subsequent queries, generate session-id or token and pass that. In the below section, I will discuss about session-id and tokens.
Session or Token?
You can use either session-id or token for REST or GraphQL APIs. What is the best approach?
First, you have to understand HTTP/HTTPS, REST and GraphQL. HTTP/HTTPS is stateless. It does not store data from previous requests. Apart from Websockets and Subscriptions, REST and GraphQL are also stateless. A server does not normally remembers requests. It just serves the API calls it received.
We need to to check state sometimes. Sometimes the server needs to know if the user is authenticated. Session-id and tokens solve the problem differently.
In session-id, the server generates a session-id when user sign in. It stores the session-id. If you have multiple servers to serve high traffic, you need to store that id in a shared or distributed database. In the case of a shared database, it can create a bottleneck.
Now in token-based systems, the server creates a token which contains some necessary information. Generally, this contains user_id, role, expiration date, etc. info. This token is not stored in servers. The server sents token to the front end. When the front end needs data from authenticated APIs, it sends the token to the server along with other data. Server checks the information written in it. And then process the request.
Tokens are stateless. Server does not have to remember token. We need to know if token has been tempered. Otherwise, attackers can generate tokens to attack services.
For making scalable service, tokens are always better. It is becoming de-facto day by day.
JWT
In the previous section, I have stated tokens are better for scalable services. But, we also need to make sure tokens are not tempered.
JWT is the answer. In a JWT token, there are 3 parts. Header, Payload and Signature
. First two parts are base64Url
encoded JSON data separated by a dot.
Header
Typically header consists of two parts. alg and type
. alg
defines the name of the algorithm used. type
is the type of token i.e JWT.
Payload
It contains the important information. User id, expiration time of token, issuer, subject, etc. are stored here. Server checks different data from here.
Signature
It is the most important part. Encoded header, payload, and a secret is used to create a signature. Server has the secret. So, it can verify payload using this secret. So, the server can be sure, nobody tempered the payload.
JWT is a very popular implementation of token-based authentication system. It is widely used. OpenID connect, Auth0, etc. also uses JWT as their token.
Cookies or Session or Local Storages?
We must store the JWT token returned by the server. We need to store it in a way that can persist a long time. Users should not be signed out after closing browser. We also have to store it securely.
Session storage is short-lived. Closing tab/browser wipes out session storage.
Local storage persists even after we close browser. So, it is a very popular choice to store token. But, it is not the most secure place to store JWT tokens. Articles like this, this and this explains problem with local storage. It is succeptible to XSS (Cross Site Scripting) attacks.
Cookies are relatively smaller storage. It has some attributes like expiration date. Cookies are also vulnerable to various attacks. But, we can set the HttpOnly
flag to true
to disable reading cookies by javascript. This will help us to mitigate XSS problems in cookies.
What about CSRF?
CSRF or Cross-Site Request Forgery is the final piece of the puzzle. Attackers can use cookies to carry out CSRF. Cookies are sent with every request to the server.
Imagine you are signed in to your bank's online portal and you are browsing as usual.
Now, you clicked on a link that contained an invisible form that will be sent to that bank API without even any button click.
If this happens, cookies related to bank service will be sent to that server. So, the bank will think you called the API. And it will transfer money according to the form data.
There are two remedies for this problem. One is, setting SameSite
attribute in cookies. Another is to use CSRF tokens.
SameSite Attribute
It is a relatively new concept. Not every browser supports this attribute.
You can set one of three values in this attribute. Strict, Lax and None
.
Strict
means, no cookie will be sent to the server if the front end and server domain are different. Lax
will let you send cookies from different sites when the HTTP method is GET. And, None
is the default behavior (Attack prone).
We should remember, GraphQL uses POST requests for every API call. So, using Lax
in GraphQL will make no sense.
CSRF Tokens
These are random unique ID generated by the server. You should save the CSRF token in local storage and JWT token in a cookie. We should set HttpOnly
flag to true
. SameSite
flag to Strict
and Secure flag to true
.
We need to send CSRF token in the API request body. Now even if an old browser does not support SameSite
attribute, CSRF tokens will help us against attacks.
One has to do CSRF attack on an old browser and also an XSS attack to find out the CSRF token at the same time for a successful attack. So, by this method, we have reduced attack probability.
tl;dr
- Use HTTPS
- Use bycrypt and salt to generate hash
- Use JWT tokens for authentication and authorization
- Set JWT token in Cookies. Set
HttpOnly
and secure flag totrue
andSameSite
flag toStrict
. - Use CSRF tokens and save that CSRF token in local storage.
Top comments (0)