 Hello, everyone. We're here to talk to you about Backstage's new auth system. So my name is Patrick, and with me I have my colleague Fredrik. We're both engineers at Spotify and core maintainers of Backstage. Now let me start with a super quick intro to Backstage, which is a framework for building internal developer portals. Backstage uses a plugin-based architecture with both front and back-end plugins. Today we're going to focus on the back-end side of things, where you can think of a plugin as roughly the equivalent of a microservice. Now we have a couple of important design principles for Backstage. First off, we want to minimize external dependencies. We want to keep it simple to deploy. We also want to minimize configuration and ship with sensible defaults. Now there's a lot of power in the customization of Backstage, but we want things to work well out of the box. Now over to Fredrik to talk about her new auth system. Names and stuff. Thanks, Patrick. So our auth system was really due for a revamp. There used to be a requirement for Backstage to be deployed behind secure gateways, for example, a VPN, and that was due to the design of the old auth architecture and its limitations. The goal for this new system has been to make installation secure by default so that they are safe to expose toward public internet, and while at it improving service-to-service auth and user token management. So let's start by looking at user auth in the old design. So this is what it looks like. We built an auth backend plugin that is the basis of all user-based auth blows. It receives a sign-in request from a client, and then ensures that there's a fresh public-private key pair that it used to sign a JWT that it returns to the client, and that proves who you are. And the auth backend also has a JWTS endpoint that contains all the public keys exposed. The client stores the token in memory for later use so that, for example, when it wants to perform a search or call any backend plugin, it passes along this user token, and the search backend knows how to turn around and ask the auth backend to verify this thing by talking to its key set endpoint. And this port is actually pretty okay. This is the previous design, again. It's mostly kept around, but with a small important addition, but we'll get to that soon. What if you need to make an upstream request as a backend while fulfilling their initial call? So in the old design, the user token was just forwarded to the upstream service. You know, it's needed by the receiver sometimes because he wants to do, for example, permission checks and so on. And this is not great. Upstream services have no way of knowing who's actually calling them. It just looks like a regular user request. And it leaks tokens a little bit too much through the call chains. Since user tokens has the full auth power of the original user, we want to constrain the use of those as much as possible. There's also the case about pure service auth when there's no client interaction at all. For this, the old design had a secret key in app config that all plugins shared, and that was used to both symmetrically sign and verify tokens as they were passed along. And this has definitely had several shortcomings. The receiver only knows that the caller is like some backend plugin because apparently they knew the signing secret, right? But there's just one secret. You have to rotate it manually, which is impractical. And the generated tokens also aren't properly limited in their use because there's no source or target plugin info in them. So once you have a token, you can talk to anyone. So how do we improve all this? Let's start by looking at service auth. Now it's in the new design. If you remember the auth plugin and its key info, now all plugins have this. So plugins create self-signed service tokens, which are only valid for one given pair of plugins. And the receiver can use the service discovery mechanism to turn around and ask who the claimed caller is and verify against their key set that is actually issued by them. And the token can no longer be used against any other backend. And we get key rotation for free. Looking at user auth, this is the little edition that I talked about. Signing now returns also a secure identity proof, as we call it, inside the user token. This is an entirely self-sufficient little thing that can be validated on its own. And as a matter of fact, it can be embedded inside service tokens as well. So when clients make requests, it gets embedded and turns the service token into an own behalf of token. And now the receiver can safely and separately know that identities of who's directly calling them and who the original user is can be verified. This identity proof can also be used for other things, like making cookie tokens that identify the user, but do not have the full power of the original user token, which is important. So back over to Patrick. All right, thank you. Okay, that covers the architectural design of our new system. I want to talk a bit about our API design. So as a reminder, one of the primary reasons for these changes was that we wanted to make backstage protected and secure by default. But that's a quite tricky change to roll out. We could have required all plugins to opt in to this new behavior, but we chose the opposite. They are instead always protected by default, which we call default off policy. Now, plugins with particular requirements are able to opt out of this if they need to, for individual routes, which is what you see here in the code snippet to the right. Now, the reasoning for this choice is simple. It has a higher impact in the very short term, but long term it reduces the risk of mistakes and makes the default behavior the secure one. We really want to avoid a situation where there's uncertainty about whether plugin is protected or not. And if a plugin is broken, it's because it rejects access rather than the other way around. Reject access when it shouldn't. Now, that was an example how we bring existing plugins along. Now, I want to highlight an example of how we prepare for the future. So our new OAuth APIs has a method that execs incoming requests and returns its credentials. They in turn represent either a user or a service principle, which we in this example used to decide the level of access. Now, that's fine for now, but consider what would happen if we added another type in the future. Let's say some form of guest access. Now, we've got the vulnerability on our hands because guest principles are treated just the same as the service. You could go blame the author for this code for not being explicit enough, but that's not very productive. After all, we effectively encourage the code to be written this way, which is why we chose a different design. So this is our actual API. If you want to get access to the principle, you have to explicitly list the types that you expect, which is the allow option you see here at the top. With this design, we're able to add additional principles in the future without breaking existing code in dangerous ways. And that's it. Thank you.