Skip to main content

Secure your Modular Monolith by pretending it's a cat

About this video

This session was presented at NDC London 2025.

Cats require attentive care, consistent boundaries, play, and a cozy, secure environment. Just like Modular Monoliths! While everything is still within the cozy confines of the monolith, security is easy! But let me show you how it becomes harder when you start scaling out your modules, and how it pays off to plan for it upfront. Just like getting a cat!

In this humorous talk, you’ll learn which security concerns are important to think about while working in a Modular Monolith. I’ll show you how you can implement those concerns so that security isn’t a disaster when you need to scale your system out. And maybe you’ll learn a thing or two about cats, too. Dog people are also welcome.

đź”—Transcription

00:09 William Brander
Okay. May as well get started. Welcome everyone. Thank you for choosing to spend this slot in this session. You could have been anywhere else in this building learning a lot of useful things, but instead, you're here to talk about cats. It's a good use of your time. My name is William and I want to start with a disclaimer. So I am not a cat person. Now, for those of you that are dog people in the room, don't get too excited because I'm not a dog person either. I think if I'm limiting myself to be a one type of animal person, that's a bit too restrictive. I like dogs, I like cats, I like mice, I like birds, I like goats, pigs, cows, anything. As long as the animal is affectionate, wants to play with me, enjoys head scratches and cuddles, it's a great animal to be around.
00:54 William Brander
In fact, when I was growing up, my grandparents had a farm, and on that farm there was a cow... I'm sorry, you deserve better than that. You're not going to get better than that in this session though. So too bad. They had a cow. And this cow was my favorite cow in the entire world. It was this light brown color. If you squinted in the sunlight would have a little bit of red tinge to it. It was a very cute cow. I called it Rex. She was probably about this tall, although I was a lot shorter then. So in hindsight, probably about this tall. And in the mornings, I used to wake up and leave the farmhouse area and go to where the animals were kept, the livestock. And as I walked through the gate into where the animals were kept, Rex, this cow would come running towards me, very, very happy to see me and we would play and walk and she'd follow me around all day.
01:44 William Brander
In fact, I used to play fetch with her, so I would take a stick or a seed pod or something like that and I'd show it to her and I'd throw it and I swear, every word of this is true. So I would throw the stick and she would completely ignore it. But when I went to go fetch the stick, she would walk along right next to me, bumping against me having a good old time. So any animal that likes affection or likes to play with people, that's what I'm into. Now, in this session we're going to be talking about securing modular monoliths, and cannot say that word. Security is a big topic. There's consultants that make a lot of money going into a lot of detail about some of these things. I'm not one of those people and I want to make sure that I'm giving you what you want out of this talk.
02:27 William Brander
So before we spend an hour and I don't cover what you want me to cover, I'm going to list exactly what I'm going to talk about for you so that you can decide whether or not you want to go and learn about Git or something else. So you're not forced to sit in the session and get right to the end and then say, "Well, William didn't cover what I wanted," otherwise, you're not having a good time. So we've got the dog things on the left. Those are the things I'm not covering because this is a cat talk. So all the things on the right are what we are going to be covering. I'm not covering authentication. I'm going to assume that you have some way of authenticating users within your application and when they present themselves to your application, they've got some token that proves that they are authenticated correctly.
03:07 William Brander
I'm also going to assume that you've got TLS encryption set up between all the components of your system. If you don't have MTLS or you don't have transport level security between the bits of your system that you're talking to, I'm not going to tell you how to set that up in this case. I'm not going to be covering token exchanges, but I will cover briefly, why token exchanges may be useful. So token exchanges are where you take one token, you swap it out for another one before you do another request. I'm not going to cover the tools and techniques that allow you to do that, but we will talk about why you might want to. I'm not going to tell you how to secure databases or file shares or anything like that. That's honestly not in the area that I spend a lot of time in, so I'm not the right person to tell you how to do that.
03:49 William Brander
And then finally, I'm not going to cover any front end security because I am not a front end person at all. In fact, my front ends are typically so secure because nobody can use them. They're so horrible and that's why I won't be showing you how to do that. What we will be covering in this talk though is we will cover why tracking identity is important, especially in the context of a modular monolith. We'll also take a look at the basics of zero trust in messaging systems. So a modular monolith in essence, is a message-based system, and we're going to take a look at zero trust and why parts of it would apply and which parts are important to look at and which parts you can safely ignore.
04:28 William Brander
We'll also take a look at personalization of data. So personalization or in application level security. There's three phases. You've got authentication, which is, I am William, this is the proof that I'm William. You've got authorization, which is I'm allowed to do this. And personalization is the third aspect of that where you get a slightly different experience based on who is accessing the system. We'll also cover token exploration and why that's something you need to be aware of within modular monoliths. And then we will cover how versioning affects the security of your application. So as you deploy V-1 and V-2 V-3, what that means for the security in your system. And because I know there's at least one or two of you in the audience for it, you don't have to worry, there will of course, be cat memes plenty throughout this talk. So do not worry about that if that's what you're after.
05:17 William Brander
I'd like to introduce you to the inspiration for this talk. This is Butterbean. He's my cat. Everybody calls him, Butt, not because that is short for Butterbean, but because he's an asshole. He's the grumpiest cat I've ever met in my life. He doesn't like anybody except my daughter, which is why I can safely say, you are not his favorite person because you are not her. She's not here. He only likes her and that is it.
05:43 William Brander
So let's take a look at a modular monolith. What is a modular monolith? It might be useful to start with the word monolith and then see how modular fits into that. So a monolith is an application that is deployed as an entire unit. It will be one application that you run all of the code for the system, all of the capabilities that your system exposes, live within that one code base and that one application process. This makes development really easy because it's just one application that you're working on. It also makes deployment really easy. But the downsides are, usually, it's easy to get tight coupling between the different components within your monolith, and then you end up with what we call a big ball of mud, which is really difficult to work with. So a monolith is one application that is deployed.
06:34 William Brander
On the other end of the spectrum, we might have something like microservices or you're going further and just do functions as a service. With microservices, each capability that your application exposes would be deployed as its own separate process that you could run. And you could then manage those via Kubernetes or containers or whatever you want. So the benefits of microservices are, because they communicate via messaging and they don't invoke code directly, it's much harder to introduce tight coupling between the different capabilities of your system. But the downsides are, it's really difficult to work with that development time because you might have to spin up 20, 30, 40 different instances of these different components so that you can actually get your system working as a whole.
07:19 William Brander
A modular monolith is a hybrid between the two. It's a compromise where you develop your application as a monolith. Everything is deployed internally within one application, but the components within that application, the modules never communicate directly with each other. They would instead communicate via messaging through an implementation of the mediator pattern. So you would have the mediator pattern and all the modules within your monolith would send messages or publish events and the mediator pattern would route them to where they need to go.
07:51 William Brander
What this enables then is that if you ever do need to take part of your system and scale it out separately, so if you take one module within our application, we can take that module out and move it to a separate runtime and have it run on its own there. Because it's no longer in process anymore though, we need a way of communicating with it still. And in this case, most people would introduce some type of message broker and then that module would talk to the message broker and the implementation of the mediator pattern would decide whether or not a message needs to leave the process. And if it does, it'll put that message on the message broker and then route it to the module that has been deployed separately.
08:30 William Brander
So a modular monolith, the intention behind it is to still maintain low coupling while getting ease of deployment and ease of development so that you can get options in the future to scale things out if you need to. And this is exactly like getting a cat. The analogy might be stretched a little thin, but it's exactly the same because if you get a kitten, the first thing you do is you don't deploy your kitten within an application runtime. You deploy your kitten within a safe room in the house and you typically keep the kitten there for somewhere between a week and two weeks until they're comfortable with the house or the room that they're in. Instead of an API gateway, you'd have a name that you would interact with the cat with and instead of modules, you'd have different capabilities that your safe room has for the cat. You would've toys for the cat to play with. You'd have food that it can eat in the room, you'd have a litter box because food has another part to it that you need to deal with.
09:24 William Brander
And I will admit, the analogy breaks down a little bit here. So instead of a mediator bus to talk between these components, you'll just play with the cat and give it affection and attention and let it know that you are now its people. Eventually though, you can start taking some of these capabilities out of the cat's safe room and move it somewhere else. So you can take the litter box and move it to the bathroom for instance, and now suddenly, the cat has slowly got access to more of the house and you can eventually let the cat play with the rest of the house. This is a picture of Butterbean when he was a kitten. Originally when we got him from the rescue center, they said he was a girl. So that was weird. That's why he's got a little pink collar on and his name was Cammie back then.
10:03 William Brander
So let's take a look at what happens when you let your modular monolith out. So once you take your monolith and you scale something out, what does that mean for security? This is a picture of a cat called Jan. It's a friend of mine, Rene Nijkamp who's got this cat. It is not a picture of the cat but, it is a picture of a cat but, so I think it might still work. Now, when we take our modular monolith and someone interacts with our system, what they would do is they would present some user token to say, "I am William. Here's the token that proves that I'm William." And all they have to do is pass that token into the API gateway. And then most of the frameworks used to develop this will be able to read the user token from system thread, current principle or something like that.
10:47 William Brander
You can just get the context of who is using your system from the code of the new application. And this works really well. But what happens if you move a module out? So this module that we've moved out, we've scaled this part out. Now, our application needs to talk via message broker and then our mediator pattern talk to that message broker. So we are able to get the parts of the system talking to each other, but we no longer have that identity associated with the user in process because module C is now out of process, we don't have the user information.
11:24 William Brander
So what we tend to do is when we send a message from a module within our system, a good approach is to include some representation of the user identity along with that. So as the message flows through the mediator pattern, across the message broker and back into the inbox of the module that we've scaled out, if we include the identity as part of that message, either as a header or explicit property on the message, it means that we've got that identity that persists even across a separate process. So an example of doing this might be if we have a charge credit card message, perhaps just adding a property on there that says this is the user ID that triggered this action. We'll revisit this a little bit later in the talk because this doesn't always work, but you need to at least think about having some way of passing that identity across the application when you're working with it.
12:14 William Brander
So, Butterbean loves people, person, I should say. He loves my daughter and he loves to lie on her chest, but everything he does has to be around her. For him, the action of the person is involved in everything he does. If she's in bed or at school or something and he wants attention like this, he'll settle for someone else's chest so the person is still involved in what he's doing. It's really annoying when you're trying to read a book because if you're lying in bed reading a book like this, he'll put his head here and bump you and annoy you and eventually, you have to lie like this. So you can read. It's really annoying thing to do.
12:47 William Brander
But when you are in the modular monolith and you're going to send a message, start thinking about, well, can I package a user as part of this message? So that when I send it, it's got some identity that goes along with the message. And we'll revisit ways that you can do this in a little bit as well. So even when you're working within a modular monolith, long before you think about scaling parts out, include a user identity as part of the messages that you send so that you don't rely on having that information contextually available later on that'll make your life a lot easier in the future.
William Brander
What about using the custom data structure? So this is where before you send the message, instead of just appending the user ID to the message, you add some additional information to the message as well. You might create a user identity object, which includes the claims that the person has got. And then you would have a hash of this user identity object so that nobody could tamper with it. So that when you receive the message, you can then go and say, "Well, the user at the point in time that this message was sent had these claims, so therefore they're allowed to do this." Again, there's no expiration here. So you work around the token expiration that way.
33:28 William Brander
But very similar to just sending the user identity, if somebody can get access to the message that you sending, they can copy that user identity object and put it on a different message. So they might be able to tamper with future messages. They can also do replay attacks where they take a message, offer the queue and then put it back on the queue five, six, seven, eight times. So that message could process five, six, seven, eight times because the content of the message is still valid, but they've tampered with your system.
33:55 William Brander
So the third option that we're going to look at is token exchange, and that's where you separate the internal part of your system from the external part of your system and logically, in your mind you have two separate things. So the user comes to your system and they present their JOT token, and this is represented as them presenting external identity to you. You then say, "Okay, that's fine. You are who you say you are. Your JOT token is still valid, it hasn't expired yet. So what I'm going to do is instead of attaching your JOT token to the message, I'm going to replace that with my own JOT token." So the module itself would have its own token that represents who it is. The core part of this is that those tokens need to have a longer expiry, and you're making an assumption that this is now internal to your system, so you're thinking that there's going to be a higher level of trust here. So it might be okay to have a longer expiry in this case.
34:50 William Brander
It must also include a hash of the message, a full hash of the message, which includes the message ID. So every time you send a message on most brokers, you get a unique message ID for that message. You can use it for deduplication or implementation of the outbox pattern. And if that message ID forms part of the hash of the message, it means that nobody can do the replay attacks because they can't take the message and copy it again because it'll have a new message ID and that hash will be different. So you can validate that people aren't putting duplicate messages on your queue that way. You could also use refresh tokens for this. So refresh tokens, when you get a JOT token, depending on the provider, you can request a refresh token, which basically says, "I've got this token, it's going to expire soon, give me another one." And it'll give you a new token to represent that. So you could do the same thing with refresh tokens as well.
35:42 William Brander
Ultimately though, no matter which technique you decide to go with, you do need to be aware that when you start scaling parts of your modular monolith out, you are going to have to deal with expired tokens. It's going to be a reality. So you need to start thinking upfront with how do you deal with that? Are you okay with just sending the user identity or do you need a bit more security along the way as well? So that if you do have a system that's really slow, this is an actual picture of the performance of my code. If you do have a system that's really slow and by the time the message gets to the front of the queue, you know that you're going to be able to process that message as well.
36:22 William Brander
Let's have a look at versioning systems. So when things change, so I got Butterbean when my wife and I got married many years ago. I could still grow here then. Life has changed a lot. We've had children, we've had mice, we've had birds, we've had dogs, and he made sure that we knew how miserable he was every time something changed. So let's have a look at how we can deal with changes within our systems.
36:46 William Brander
Now, version changes are when you make a change to your software and you deploy a new version. It's not always going to be a problem with modular monoliths that have been scaled out, but sometimes, it is, especially if the version changes that you're doing change the message types that are sent to the broker. So if we send a message from module A to module C, and everything just goes fine, that's great up until we change module A, or we change the rest of our modular monolith and we make some sort of change to the message contract. Maybe we are sending the charge credit card message, but instead of just sending the whole thing like this, we decide, well, the credit card number is sensitive, so some people should be able to read it, but other people shouldn't be able to read it.
37:27 William Brander
By changing the contract of the message, we need to be able to decode that message in module C. So if we send a new version of the message, module C needs to also be able to read that new version of the message, but it's not quite as simple as just deploying a new version of module C, because there could be other messages in the queue for module C that were sent using the old format.
37:53 William Brander
So you've got two different formats of the messages on the same queue that are going to get processed by the same endpoint, and it needs to be able to deal with different message contracts. So when that message flows, if module C is able to decrypt both of those different properties or is able to deal with different message formats, then you'll be able to process those messages successfully, otherwise, it's going to fail. And a way that we could implement this is very similar to how we've done everything before. So we sign the message when it gets to our mediator pattern, then we send the message along and the message broker says, "Cool, here's the signed message." I don't know who signed it or what key it was signed with, but good luck. And we could try and decrypt it with all keys that we've got access to. That's going to work, I suppose, badly, and maybe your code will start performing like mine then.
38:44 William Brander
But another approach that we can do is when we send the message, we also include the key ID of that message. So that's the identifier of the key that was used to sign that message, which means that we can then use any number of different keys to sign messages as long as we have some sort of secret management tool that we can use. So AWS KMS or Azure Key Vault or HashiCorp Vault. What you do is you receive a message and it's key ID one. So you go to your secret provider and you say, "I would like to get access to key ID one, please give me the key so that I can decrypt the message." And the vault that was then responsible for saying, "Yes, you are allowed to have access to this specific key," or, "You are not allowed to have access to the key."
39:26 William Brander
And when you've got something like this in place, you can do really quick key rotations for your security and if you've got really quick rotating keys, it's a lot harder for someone to be able to get access to that and do something in your system. If you've got constantly changing keys, it's much harder for someone to be an asshole to your system. So your middleware that you're using should be able to decrypt using different keys and you should practice using different keys for different types of messages. Leverage some of these secret management tools that are able to do this for you.
40:02 William Brander
So, a quick recap and then we can escape. I know it's been a long day for everyone, so I thought let me try and make this a little bit of a shorter session so we can be a bit more energized. Think about passing identity with your messages at the beginning, even when your system is still a modular monolith because once you've got identity as part of the message flow, it's a lot easier to keep that identity when you start splitting things out later. Leverage your middleware to be able to sign messages and decrypt messages so that you can make sure that only people that are supposed to send those types of messages are able to. And then validate those signatures. And then do the same with the encryption so that people don't get access to your data. Obviously, you don't need to do all of these, but if you are in an environment where zero trust applies, then you can start looking at these techniques for signing messages and decrypting messages.
40:54 William Brander
Personalization is a great one, and this makes a lot of sense even when you are still within the confines of a modular monolith because when you're publishing messages within that monolith, you can decide who gets access to those secrets and who's able to read those sensitive properties on the message. Be aware that token exploration is a thing and you are going to have to deal with it. So make sure that you know how you will deal with it in the future and use something like a vault to manage your secrets for you so that you can do frequent key rotations and you can use multiple different types of keys to be able to send these messages securely. If you do that, you have a good time having a fun little system that you can work with. If you don't, that's also fine. I suppose none of these are hard and fuss rules that everybody must do.
41:42 William Brander
Originally, when I was coming up with this talk, Butterbean wasn't going to feature at all. It was just going to be cat memes because they're objectively hilarious. On the ranking order of humor, sarcasm might be at the bottom, but cat memes are right at the top as perfect forms of humor. Unfortunately though, Butterbean passed away while I was preparing the talk, and I thought, what better way to honor his memory than to get up in front of a room full of people that didn't know him and told them all what an asshole he was for an entire 45 minutes of their lives. And I hope that maybe as you go back to your day jobs and you start thinking about, well, do I need to do anything about the security in my system? Maybe you'll think what an asshole this cat was and how miserable he made my life, and you'll smile. What a grumpy asshole of a cat that are miss terribly.
42:31 William Brander
Thank you. I do have plenty of time for questions. Otherwise, if you want to catch me downstairs, I'll be at the Particular booth. I didn't even say that. I work for a particular software, we make NServiceBus, which is, if you want to distribute your systems and scale things out. Obviously, I didn't talk about any of that there. So if you do want to talk to me about any of either zero trust, I'm quite interested in zero trust in messaging systems, or if you just want to talk about cats, come grab me at the Particular booth. I'll be there after this and tomorrow. Thank you.