Defense in Depth: Identity modelling (part 1/7)

Läs den här sidan på: 🇸🇪 Svenska
19 September 2023

Virtually all of the systems we are building today share data via public networks. We rarely want that data to be available to everyone, so we restrict access to it.

We often also want to differentiate between different types of users and give these users access to different types of information. A user at one level should not be able to gain access to information on a different level. We also need to protect our systems against targeted external attacks.

To meet these requirements means that we need to build security in layers and multiple parts of the system. One of our most important decisions is how we will implement access control. How and where we do this affects how secure our system becomes and how easy it is to modify it later.

In modern software architecture, the approach of securing systems using only network solutions and other types of perimeter defense is proving to be less and less viable. Building defense in depth with strong access control requires the individual APIs of a distributed system to verify access and identity independently.

Perimeter defense vs. Defense in depth

The system on the left is protected by IP-based security using a firewall product. All traffic within the firewall is “Full Trust” and has access to the entire system.

In the system on the right, our services are protected in depth using multiple layers. Our services apply “Zero Trust” and consider all incoming traffic to be insecure. The system on the right is a more robust solution since we base security on identity, making it more flexible for operations and integrations.

Many of the systems we design today build on breaking down the domain into smaller, independent services. Integrations via public networks are often mandatory, and the system needs to ensure strong authentication from connecting clients.

A shared understanding of identity in the form of a centralized Identity Provider (IdP) is fundamental for all services to make correct decisions on access rights as the number of services grows. Identity is a central and common concept, and the interpretation of what access an identity should get is part of every service.

To establish a secure system that is flexible over time, as it grows and changes, we need to model which user types, clients, and resources exist within the system. This model gives us the tools to define what should be part of a central identity and what should be part of local access control for our system.

This article looks at how you can model identity and use OAuth2 and OpenID Connect to build a secure API. These protocols offer us a few different concepts that we can work with:

We start by creating a picture of all the user types, clients, and services that make up the system. Let’s take a look at an example:

Modeling Identity, Blank

Scopes — are rights that we assign to clients, and it’s worth finding a granularity that is right for your particular system. This is where you can put your domain model to good use.

Audience — is a grouping of the APIs that make up our system. A good way to start is to have one (1) audience per API. However, we can group in any way we want, e.g. , one single audience representing all APIs.

Now it’s time for the actual modeling process. One approach is to draw arrows from user types to clients and from clients to services. The resulting picture allows us to identify who uses what and what level of granularity our model needs to support. In our example, the following model might work:

Modeling Identity, Filled

product.read Grants read access to products
product.manage Grants permission to read, create, delete and manage products
order.read Grants read access to orders
order.create Grants permission to read and create orders

The choice of audience and scope affects how much we can limit a valid ticket in our API system. In our example, if we had one audience for all APIs, we would be unable to restrict a token with an order.read scope to just the statistics API. When combined with scope, audience give you the granularity you need.

An Identity Provider (IdP) can allow the user to combine scopes with audiences when logging in. Things look slightly different in different IdP products, but in principle, the user grants their client the right to use a selection of scopes for a selection of audiences. For example, in our case, an administrator can grant a client the right to manage products for the product API but nothing else. This selection is often referred to as User Consent.

Finally, we have our permission model, or what access rights a user has. Each API receives information on what scopes and audiences the user has approved, their identity, and details from their login. The next article will discuss how we use this information to build a strong access control system.

Every API implements access control for what Emma Smith is allowed to do. In Article 3, we will take a deep dive into what you need to bear in mind.

Joel Harsten, Omegapoint

I often see people forgetting about access control altogether. You need to be diligent when limiting access to resources so that you don’t base this solely on a valid token, but also on your rights in the system.

A documented and well-designed identity model is the foundation of any strong access control implementation and secure system. Our experience has shown that finding a model that balances the central versus the local, that isn’t too simple or too large and can be adapted to future needs, can be a challenge. Well-designed domain modeling is a great way to start this kind of work.

Tobias Ahnoff, Omegapoint

Beware of recycling clients. According to the “Least Privilege” principle, each client should have the minimal amount of permissions they require for performing their duties. If you recycle clients, you run the risk of giving clients more permissions than they need. You might also make traceability in the system more difficult, and you risk creating dependencies that make it hard to rotate credentials.

One good starting point is never to recycle clients between different bounded contexts.

You need to find a balance between having many clients to manage and few clients with lots of permissions and reduced traceability.

See Defense in Depth for additional reading materials and code examples.


More in this series: