Stateless is a software architecture where the application holds no information about its operating state or the conditions in which it exists. This contrasts with stateful applications, which do maintain a persistent state throughout their lifetime.
Going stateless might sound complicated, but it can actually simplify system deployment, maintenance, and integration. This article explores what stateless is, how it's implemented, and why it's so beneficial to real-world systems such as authorization services.
The state of a software system refers to the information available when a function is performed, such as the identity of the logged-in user. A stateful service persists this information, while stateless services reestablish the state each time they're used.
The paradigm shifts the responsibility for maintaining state onto the client. For example, in a stateless authorization context, the client should provide the authorization layer with the context describing the user issuing the request and the resource they’re trying to access. The service can then use this information to deliver an authorization outcome without having to fetch any further data itself. As a result, each request is completely independent of any other, and the authorization layer has no external dependencies.
Stateless systems have several advantages over their stateful counterparts such as better scalability, improved fault tolerance, and simplicity in implementation.
Their characteristics make them easy to understand and reason about. Each session takes the form of a self-contained transaction where the client issues a request and receives a response. The interaction is handled in the same way each time it occurs because the service holds no state that could affect its outputs.
The following are a few other benefits of going stateless:
Stateless architecture has received more attention in recent years due to the rising interest in microservices and serverless computing.
However, stateless is a well-established strategy that underpins some of the software systems we take for granted:
Stateless architecture is particularly beneficial to authorization layers. Authorization, the act of checking what a user can do within a system—as opposed to authentication, which verifies a user is who they claim to be—is traditionally implemented as a stateful server-side process, requiring complex dependencies to fetch the user’s details and the permissions they hold. This impedes scalability: database performance will reduce with load, and any synchronization problems could cause different instances of the service to return conflicting authorization outcomes.
By contrast, stateless authorization facilitates straightforward decisions with predictable results. The application can fetch the state it requires upfront then present it to the authorization layer as a signed token. The token provides the service with all the data it needs to produce an authorization decision, eliminating external dependencies.
The stateless authorization layer is therefore simpler, more scalable, and easier to implement, especially when multiple clients need to interact with the system. It removes the need to persist and synchronize state across the authorization layer’s instances. Although fetching the state within the application can appear to be an overhead, in reality, most systems will already be holding the required information on the client.
Stateless authorization services use the following high-level architecture:
1. The application calls the authorization layer when it needs to check whether the active user can perform a particular action.
2. The data sent to the authorization layer includes everything that the service needs to produce an authorization outcome, such as the user’s identity, any permissions or special capabilities they hold, and the identity of the resource that’s been requested.
3. The authorization layer evaluates the provided data and delivers an authorization result (either allow or deny) back to the application.
4. If an “allow” result was issued, the application permits the user to perform the requested action against the resource. Otherwise, the user is deemed unauthorized and is blocked from continuing.
Crucially, all the information required to produce the authorization result is provided by the application. The authorization layer isn’t responsible for fetching any details about the user or the resource.
As we've seen above, implementing a basic stateless authorization system is relatively straightforward. Once you've issued a signed token to a client, it should be able to present it to any instance of your service to access protected resources.
Nonetheless, the difference between a simple and a production-ready authorization layer depends on the details. Here are some best practices to keep in mind.
The integrity of stateless auth depends on proper token management. Fortunately, standards such as JWT have solved most of the challenges in this space. It's safest to issue and consume them using established community libraries such as jwt-go for Go or jsonwebtoken for Node.js. Rolling your own code for these functions can make you vulnerable to security issues if your signature verification proves to be incorrect.
It's also important to plan how you'll handle token expiration and revocation. Ensure that each token carries an expiration time that your service checks during its authorization procedure. If you need to immediately expire a set of tokens, you can rotate the private key that the server used to generate their signatures.
Note that stateless does not mean no persistence at all—stateless in this context refers to not holding the state that's relevant to a particular client. For more precise expiration controls, you can maintain a central index of issued tokens. When a client presents a token, retrieve the token ID from its payload and check there's still a match in your index. This allows you to revoke tokens by deleting them from the index.
Any authorization data should be communicated over a TLS-protected connection. This helps prevent leaking of sensitive values such as passwords, authorization grants, and access tokens.
Tokens such as JWTs aren't encrypted by default—anyone can retrieve a payload by Base64 decoding the token. HTTPS mitigates the risk by preventing bad actors from eavesdropping on authorization activity, but in especially demanding situations, you might also want to encrypt sensitive payload fields for an additional layer of protection.
Use a centralized authorization server to store authorization data such as users, roles, role assignments, and details of issued tokens. This permits you to make changes in one place and then immediately apply them across all your services.
Choosing a decentralized approach can be attractive at first, but it tends to become less manageable over the life of your service. It forces you to maintain several authorization server instances and ensure stable synchronization between them. Problems can cause errors and discrepancies to occur.
Centralized authorization eliminates these problems. Although it creates a single point of failure, you can mitigate against this by running multiple replicas of the authorization server to achieve high availability. The overall experience will be more reliable and easier to configure than decentralized systems.
Frameworks such as OAuth and SAML are purposely designed to simplify and standardize authorization procedures. Using them as the basis of your stateless authorization implementation will reduce the risk of security oversights and improve interoperability with other services, such as identity providers.
When you build your own authorization protocol from scratch, you're responsible for defining how services and clients interact, the ways in which tokens are used, and how authorization is requested, validated, and granted. OAuth and SAML provide a reference for these high-level characteristics, allowing you to focus on building the app-specific authorization policies relevant to your users and resources.
Stateless services don't persist any information between client sessions. They restore their state for each new session, using information provided by the client.
Stateless architecture is particularly ideal for authorization systems. The ease of scalability and absence of server-side synchronization logic allow you to develop decoupled authorization systems that are both reliable and simple to maintain. The resulting systems can be easily deployed to distributed environments, ready for use by multiple independent microservices.
The next time you're implementing authorization in your software projects, consider exploring stateless authorization. Using established stateless-compatible protocols such as OAuth 2.0 allows you to easily integrate with identity platforms and reduce the complexity in your apps.
Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team
Join thousands of developers | Features and updates | 1x per month | No spam, just goodies.