← Academy Blog

How to build a secure web application?

Every user wants their data to be safe. No one wants to share all their personal information with unknown people on the Internet. Therefore, the security of the web application is critical. This has already become a standard in the modern world. You would unlikely want to use a site where you can see such a message.

site_on_http

Before discussing the specific details of how to make the application secure, it is necessary to clarify the main rule of security on the Internet:

Data should be fundamentally protected by design, not merely hidden.

It will be found sooner or later having hidden the key under the carpet. So, let's find out what can be done to make the site safe for users.

HTTP to HTTPS

The HTTP protocol sends the vast majority of requests on the Internet. HTTP requests and responses can contain sensitive data, such as login credentials, credit card information, or other data you wouldn’t want to share with anyone. As network packets go to their destination, they pass through many intermediate devices, each of which can potentially access and steal the data from the package body. Therefore, we cannot ignore the limitation that anyone might read our data packets. This is where encryption (not to be confused with encoding) and HTTPS come into play, providing essential security.

HTTPS stands for HTTP Secure and combines the HTTP and SSL/TLS protocols.

The HTTPS protocol ensures that during the connection process, the client and server agree to use a temporary key, which is used to encrypt and decrypt messages. The key is known as a 'session key' because it is only valid for the current session on the site. A new key will be generated for each session.

The method in which only one key is used for encrypting and decrypting messages is called 'symmetric encryption.' Its main challenge is safely transferring the key from one party to another. In the operation of the HTTPS protocol, another method is used to solve the issue — 'asymmetric encryption.'

The main idea of asymmetric encryption is that it uses two keys (private and public): one can encrypt messages, while the other can decrypt messages encrypted by the other. The public key is made freely available to clients and used to encrypt the session key, but the private key is kept secret and used to decrypt the session key for HTTPS connection. If we set aside technical nuances and simplify, establishing a secure connection schematically looks like this:

HTTPS

A legitimate question arises: why not use only asymmetric encryption? Why use both methods simultaneously? The answer lies in performance: symmetric encryption executes faster than asymmetric encryption as it requires fewer computing resources for encrypting and decrypting messages.

To configure HTTPS on the site, you do not need to do many actions, the browser and server can do all the encryption and decryption under the hood. All you need to do is specify the site's certificate to the server, including information about the site and its public key.

You can generate the certificate by yourself (for example via OpenSSL), but if you use it on your website, the browser shows you the next message:

self_signed

Why is this happening? Because browsers have built-in protection against certificate forgery. Each certificate has its issuer, the so-called Certificate Authority (CA). The CA puts its electronic signature on the certificate (private and public keys work here again), so it is possible to check whether this CA issued this certificate. There is a list of CAs that browsers trust by default. In turn, the CA does not simply issue a certificate - there is a mandatory validation, for example, that you own the domain for which you request a certificate.

To see all certificate problems which can be you can visit badssl.com.

Authentication vs Authorization

Authentication and authorization are essential for web security. They are responsible for slightly different things. Authentication verifies the identity of a user, and authorization determines what an authenticated user is allowed to do.

How can we make these processes safe so that our users are not compromised and do not gain access to what they should not? Additionally, we want this process to be secure but also fast. Consider this: if we transfer sensitive user data, such as login credentials, with each request and verify its accuracy on the server each time (which likely involves a database query), it will initially add extra time to each request. Moreover, we prefer not to handle such sensitive data frequently. Even though we can now securely transmit data via HTTPS, the data remains visible in the browser and server, proceeding with its challenges.

This is where tokens come to the rescue. An access token is a tiny piece of code that contains a large amount of data. Information about the user, permissions, groups, and timeframes is embedded within one token that passes from a server to a user's device.

Tokens can vary significantly. Some are quite primitive, like Basic tokens, which simply store the login and password encoded in base64 — this is not encryption and can be easily decoded. Others, like Bearer tokens, are more complex and have become the standard for secure and fast authorization and authentication of server requests. The most popular of our days are Bearer jwt tokens.

A typical jwt token holds three distinct parts, all working to verify a user's right to access a resource. Three key elements are included in most access tokens.

  • Header: Data about the token's type and the algorithm used to make it are included here.
  • Payload: Information about the user, including permissions and expirations, is included here.
  • Signature: Verification data so that the recipient can ensure the token’s authenticity, is included here. This signature is typically hashed, so hacking and replicating is difficult.

For example, the bearer jwt token can look like this (jwt.io):

jwt

So in the payload, you can add any information that you need.

Why are bearer tokens fast? Because the server does not need to make additional requests to the database; it can extract all required information directly from the token, such as user ID, role, and permissions.

Why are bearer tokens secure? Firstly, it includes a signature that protects against forgery. This signature is applied by the authorization server using a private key — a server that we trust. We also possess the public key of this server, which allows us to verify the signature. Although this verification takes some time, it is significantly faster than querying the database to retrieve user rights or check passwords.

Additionally, the token has a defined TTL (time to live), which means it expires after a certain period (as indicated by the 'exp' field in our example). If an attacker manages to steal your token, they can only use it until it expires. Therefore, the TTL should be wisely set; if it is too short, the token will need frequent refreshing, impacting performance; if it is too long, it increases the risk of prolonged unauthorized access if the token is compromised. You can also acquire this token in various ways, from a simple exchange of a Basic token for a Bearer token to more complex processes like OAuth2.

Secure advice and protection from attacks

After ensuring your application runs over HTTPS and implementing strong authentication and authorization mechanisms, it's crucial to protect against vulnerabilities that exploit weaknesses in your code. These attacks can undermine even the most robust authentication and encryption methods if not properly addressed. For instance, XSS (Cross-Site Scripting) attacks occur when an attacker injects malicious scripts into web pages viewed by others, exploiting the trust users have in a specific site. XSS can steal cookies, session tokens, or other sensitive information accessible on the client side. Another threat, Cross-Site Request Forgery (CSRF), forces an end user to perform unintended actions on a web application where they are currently authenticated. To fully grasp how these attacks function, it's important to understand the workings of browsers, cookies, server authorization, and more. However, the overarching principle is simple: trust no one. Here are tips on how to prevent such attacks:

  1. Use popular open-source frameworks. For example, React or Angular provide protections against common vulnerabilities that could be easily introduced when developing with plain JavaScript.
  2. Sanitize Input. Ensure all user input is sanitized, meaning harmful code is stripped out before being displayed or stored.
  3. Prevent the leakage of critical information about the backend. Ensure that error messages are generic and do not reveal underlying architecture.
  4. Design the application so that if the code is disclosed, it would not result in data disclosure. Do not store access keys in the code. Do not store the password as plain text in the database.
  5. Do not write sensitive data to the logs.
  6. Stay up-to-date with the latest versions of frameworks and software.

Conclusion

Web security is dynamic, and developers must continually adapt to new challenges and threats. By understanding and implementing the best practices outlined — from HTTPS implementation to specific defenses against XSS and CSRF—you can significantly enhance the security posture of your web applications, ensuring that they are robust against current and future threats. Remember that it is impossible to make the process safe completely, and the most vulnerable place is still a person.