undo
Go Beyond the Code
arrow_forward_ios

Modern authentication in systems where trust is not enough.

Andrés Felipe Garcia Tabares
Software Engineer & Solver
October 29, 2026
To learn more about this topic, click here.

Security foundations in modern distributed architectures

Modern architectures require modern security

In modern application development, it is essential to understand the separation between frontend and backend. The frontend represents everything users interact with, while the backend handles business logic, data access, and the system’s critical processes. This separation is not only a way to organize architecture. Its purpose is to expose only controlled interfaces, such as APIs, to prevent users from having direct access to the system’s internal logic, reducing the risk of manipulation or unintended execution of functionalities.

However, separating the frontend from the backend is not enough. At this point, neither we nor our users are truly secure. An exposed API is still publicly accessible, which means that anyone with basic knowledge could attempt to interact with it and even apply reverse engineering techniques. This raises an important question: how can we trust who is making the requests? Can we assume that requests will always come from a browser that enforces rules preventing direct interaction with the API?

One of the first layers of protection is often the use of API keys, which act as identifiers to allow or deny access to services. However, this approach has important limitations, as it does not properly support more complex scenarios such as users with different roles, specific permissions, or dynamic data access. To address these challenges, it is necessary to implement more advanced authentication and authorization mechanisms. Technologies such as JWT (JSON Web Tokens) allow validating user identity in a secure and scalable way, while OAuth 2.0 enables access delegation between systems without sharing sensitive credentials. Additionally, it is essential to use tools such as bcrypt to protect critical information like passwords through secure hashing techniques.


Who does your code trust?

When an API does not implement proper authentication and authorization mechanisms, it is effectively exposed to anyone who knows its endpoints. This means that any user could perform requests such as GET or POST and access or manipulate sensitive information that does not belong to them.

It is common to assume that an API is secure simply because it is not directly visible or because it uses a basic API key. However, this approach is limited. A basic API key does not represent user identity and does not allow proper control over users. If user identity is handled by sending an ID or email in the request body, this can easily be modified by anyone, allowing access to sensitive information that should not be exposed. Additionally, if the API key is leaked, any actor can use it with the same privileges, leaving the system’s data completely exposed.


Secure identities with JWT, the foundation of trust in distributed systems

JWT (JSON Web Token) is a mechanism that allows validating a user’s identity securely without the need to maintain sessions on the server. This is especially important in modern architectures, where multiple services need to trust each other without sharing state.

A key concept to understand is that JWT does not encrypt information, it only signs it. The content of the token can be easily read since it is encoded in Base64, but it cannot be modified without invalidating its signature. This ensures that the information received has not been tampered with.

A JWT is composed of three parts: a header that defines the algorithm used, a payload that includes user data such as ID, email, or roles, and a signature generated from these elements using a secret or private key. This signature is what guarantees the integrity of the token. When the backend receives a JWT, it recalculates the signature using its own key and compares the result. If the content has been altered, the signature will not match and the token will be rejected. This process allows the system to trust the information contained in the token without needing to constantly query the database to validate the session.

This means that if a JWT contains a user identifier, the system can use that information to retrieve user data and apply business rules and authorization logic. It is also possible to include roles or permissions within the token, or manage them in the database depending on the required level of control. JWT does not define what a user can do, but it guarantees that the identity has not been altered. Authorization and business rules remain the responsibility of the system’s logic.


Storing the JWT is a critical aspect

Once the client is authenticated, it needs a way to interact with the backend, which means the token must be stored and sent with every request. This storage typically happens in the browser, most commonly using cookies or localStorage.

Although storing the token in localStorage is easy to implement, it introduces an important security risk due to its vulnerability to XSS attacks. If an attacker manages to execute JavaScript within the application, they can directly access the token and reuse it maliciously, gaining full access to the user’s session.

For this reason, in most scenarios it is recommended to use cookies with specific security configurations. By marking a cookie as HttpOnly, JavaScript is prevented from accessing it, significantly reducing the risk of token theft. The Secure flag ensures that the cookie is only sent over HTTPS connections, preventing exposure over unencrypted traffic. Additionally, the SameSite configuration helps mitigate CSRF attacks by preventing the cookie from being automatically included in requests originating from external or malicious domains.

This last point is especially important. In a CSRF attack, an authenticated user could unknowingly perform actions triggered from an external site. By restricting how cookies are sent based on the origin of the request, this risk can be significantly reduced.


CORS is not a security mechanism, do not trust it blindly

A common mistake is assuming that CORS protects the backend from unauthorized access. In reality, CORS is a browser policy, not a server-side security mechanism.

When a request violates CORS rules, the browser blocks access to the response, but the request has already been sent and received by the backend. This means that if the backend does not properly validate authentication and authorization, the action could still be executed even if the frontend never receives the response.

In other words, CORS controls whether responses can be read by the browser, but it does not prevent requests from being made. For this reason, security must always be enforced on the backend by validating tokens, checking permissions, and ensuring that every operation is protected regardless of the origin of the request.


Delegating authorization and authentication with OAuth 2.0 and OpenID Connect

JWT allows a system to securely verify a user's identity once they have logged in. However, in many modern scenarios, authentication is handled by an external provider such as Google rather than the application itself. This is where OAuth 2.0 and OpenID Connect (OIDC) come into play. OAuth 2.0 allows applications to access resources on behalf of users without requiring them to share their credentials, while OpenID Connect builds on OAuth 2.0 to provide a standardized way to authenticate users and verify their identity. For example, when a user logs in with Google, they are redirected to Google's authentication system instead of providing their password directly to the application. Once Google verifies their identity, the application can securely receive and validate the necessary tokens to identify the user and establish an authenticated session.

This process follows a similar principle to JWT-based authentication, but with an external identity provider involved. Instead of authenticating users directly, the application relies on OpenID Connect to verify their identity through a trusted provider such as Google. Once the authentication process is completed, the application validates the information received from the provider and establishes its own authenticated session. Depending on the architecture, this session can be managed using an internally generated JWT or traditional server-side sessions. This separation allows OAuth 2.0 to handle delegated authorization, OpenID Connect to provide authentication, and the application to maintain control over its own sessions and access permissions.


BCrypt for when everything else fails, the last line of defense

Even with a system properly secured using JWT and safe flows such as OAuth 2.0, there is a critical point that cannot be ignored: password storage. If passwords are stored in plain text or with weak mechanisms, a database breach can directly compromise sensitive data from all registered users.

bcrypt addresses this problem through a one-way hashing approach. Unlike traditional encryption, there is no way to “decrypt” a password. Instead, it is transformed into a hash that can only be validated by comparing results. When a user logs in, the system compares the provided password against the stored hash without ever retrieving the original password, ensuring that the values match.

In this context, bcrypt acts as a final layer of protection. Even if other mechanisms fail and a database is exposed, passwords remain protected. This strengthens the overall security of the infrastructure and helps safeguard users against some of the most critical security scenarios.

None of this is optional once your application handles real user data. 

Most security failures aren't dramatic; nobody storms the gates. They're quiet: a default that was never reconsidered, a shortcut taken under deadline pressure, a "we'll fix it later" that outlived the person who said it. 

The foundations covered here are what keep that kind of failure from ever becoming a story you have to tell a customer.

Andrés Felipe Garcia Tabares
Software Engineer & Solver
Arrow icon go to top

Start Your Digital Journey Now!

Which capabilities are you interested in?
You may select more than one.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.