Over the past month, I have been researching ways to secure web applications without relying on third-party solutions, particularly for SAAS solutions that don't deal with highly sensitive information, such as banks. My goal has been to identify simple & straightforward methods to ensure the security of such web applications.
EDIT: My original strategy of not using refresh tokens had a flaw; hence the article has been updated by using refresh tokens inside http only secure cookies.
NOTE: This article is for developers who do not want to use oauth2 or similar authentication solutions as a separate server is required for authentication in them. This is for baked in authentication that can be provided as part of REST or graphQL apis.
Below is the proposed solution.
There are two parts to any web application:
- Front-end
- Back-end
Most of my recent projects personal or work wise have a front-end framework essentially doing SSR or server side rendering.
For the back-end I use a GraphQL server.
Recently I started updating strategies for both options & I felt it was time I refreshed my approach to security.
Secure your connections
The first rule of web security is serve everything over https. For that you are required to secure both your front & back end servers with SSL/TLS certificates.
Front-end
For the SSR its all about configuring content security policy or CSP. Provide CSP header with a good policy to get relative security from XSS attacks.
An example of a CSP policy:
Content-Security-Policy: default-src 'none'; script-src 'self' https://ajax.googleapis.com; style-src ‘self' ‘unsafe-inline’ fonts.googleapis.com; img-src ‘self' ‘data’; font-src ‘self' fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-src 'self'; object-src 'none'; media-src 'self';
In the above example we are disabling everything by default and then gradually adding each source explicitly. For example we have added google fonts support; & we are allowing to make calls to https://api.example.com. For more detailed explanation review the links below:
Content Security Policy Website
Back-end
For our authentication strategy we will keep the following requirements in mind.
Cookies required:
A front-end framework that does SSR would be easily able to render on the server if cookies were used for authentication.CSRF token for security:
cookie usage requires safety for CSRF attacks on post update & delete requests.JWT token generation:
JWT access tokens will be used for authentication.Short lived access tokens:
Access tokens need to be refreshed after short period of time.Refresh tokens:
Refresh tokens will have to be used for short lived access tokens.Logout capability from the server:
For a reasonable SAAS offering there needs to be a way to logout users who’s access token is compromised, refresh tokens will serve that purpose.
NOTE: JWT tokens are a topic in itself, if you are not familiar with them use the link below to get a better understanding. JWTs are tokens that have a signature portion that can be verified by the server to see that the token has not been tampered with. The claims that are part of the JWT are not secret if the token is compromised and can be read by anyone; hence data that needs to be kept secret should not be saved in them.
Reference below links for more details:
The strategy:
The client requests authentication via username password to the server.
The server validates that username password against a database table with hashed passwords & generates a signed JWT.
NOTE: Visible user passwords should never be stored without hashing them & providing a salt value for the hash via a library such as bcrypt. This is a separate topic & beyond the scope of this article.
- The server then generates three cookies;
An http only cookie that contains a signed JWT with claims such as expiry date & the user details required by the client. As this is a secure cookie javascript will not be able to access it at all & it will be transported on every request back to the server.
It creates a refresh token possibly a guid value that expires potentially in 3 months time & stores it against the user record in the user table. It will be sent to the client in another http only cookie. The client won’t have to do anything.
A non http only cookie readable by the client that contains a x-csrf-token. This token will be returned by the client in a custom header. This is for protection against csrf attacks.
Scenario:
Lets look at a scenario how this strategy plays out.
The client logs in with username password. On success the server creates a GUID as a refresh token and stores it in the database table against the user record as a list as the user can login from multiple devices.
The server sends the access token containing the JWT & the refresh token as the GUID in two http only secure cookies that will automatically be transported back on each request.
The client on subsequent post, delete or update requests sends back the x-csrf-token value by reading it from the readable cookie sent by the server. As a third party is not able to add custom headers to a request this tells the server that the request is not compromised.
On every request the server verify’s the access token/JWT; checks the x-csrf-token value is the same one as in the sent cookie; and verifies that a refresh token value exists.
When the access token expires lets say every 15 minutes; the refresh token is verified & another access token/JWT is generated by the server & sent back in the http only cookie that will overwrite the previous one on the client. To increase security the refresh token can be regenerated again.
If a certain users access token is stolen or compromised and we need to log them out we invalidate the refresh token by looking up that value in the list stored against that user in the database table.
The next request sent with that access token will fail and the user will have to re-login.
This way the user will also be logged out of all the device where the refresh token is from. To be safe the whole list of refresh tokens can be removed to log that user out of all devices.
Conclusion:
Securing a web application requires a comprehensive approach that covers both front-end and back-end security. Implementing security measures, such as HTTPS, CSP, access token & refresh token management, CSRF protection & short lived access tokens can help protect against attacks and keep your users' data safe.
I hope I have been able to describe how we can mitigate all these issues with a reasonable & simple authentication strategy.
Photo by Micah Williams
Top comments (2)
This is a nice overview of things to consider, but it seems like you skimmed over a lot around authentication and the JWT. Those are things I always caution people on. With authentication, I think including a unique salt for each user is recommended. You also have to be careful of the hashing algorithm. With JWTs, keeping the secret key secure and using a sufficiently strong cryptographic algorithm is something to be careful of.
Have you considered using an OAuth2 provider? What things did you take into account?
Thanks.
The purpose of the article was not actually to go into the details of how to keep passwords secure; and details of hashing as people storing password locally would already have this knowledge; otherwise that is a topic for another article.
Secrets for hashing algo and JWT signing secrets obviously should be kept secure in a config that should not be pushed into code repositories.
However now that you have mentioned it I will figure out a way to include those in comments in the article 👍🏼
As far as OAuth2 is considered if one wants to use it then this article is not for them. This is for projects where OAuth is overkill, or one does not want to use oauth but simple authentication mechanism baked into either a rest api or a graphQL api that does not want a separate server.