back to index

Securing Agents with Open Standards — Bobby Tiernay and Kam Sween, Auth0


Whisper Transcript | Transcript Only Page

00:00:00.000 | Hi, everybody. I'm Bobby. This is Cam, and we're both engineers here at Auth0, and we've
00:00:21.520 | both been working a lot in the AI space lately, trying to come up with clear ways for identity
00:00:29.980 | and access control that's designed for world where agents are center stage. So that's
00:00:34.900 | included our work on our Auth4Gen AI platform, our Auth0 MCP server, and our own first-party
00:00:41.300 | agent called Tenant Security Manager. And just like everyone here, we're learning from the
00:00:45.940 | community as well as we do this. The main thing is trying to get to grips with what delegation
00:00:51.280 | and access means in a world where agents are acting on users' behalf. And today, we want to share some
00:00:58.140 | of those lessons that we've learned in this journey, and what's actually worked, what hasn't
00:01:03.620 | worked, and a few practical ways that you can get started with identity and access right from
00:01:08.180 | the start.
00:01:08.460 | But I had to splash this slide because we were told we could have opinions as long as we showed
00:01:15.600 | it. So here we go.
00:01:17.060 | To kick things off, let's talk about the security challenges of smarter agents as they start
00:01:24.240 | to do more things in the real world. And once they're moving beyond chat and actually doing
00:01:30.200 | things in the real world, cracks are starting to appear, and it's pretty clear. Secrets end
00:01:34.880 | up in prompts, scopes get too broad, and troubleshooting gets really, really hard when you don't have the
00:01:41.540 | right visibility. So agents are out there making decisions, taking actions on behalf
00:01:47.020 | of users, but it's very easy to lose track of what's really going on. Things might look
00:01:52.140 | fine on the surface right up until there's a security incident and you realize you don't
00:01:57.500 | have enough data to trace what actually happened. This is where a lot of the Gen AI risks begin.
00:02:05.380 | The OAuth calls it excessive agency, but really that's just a fancy way of saying giving agents
00:02:10.860 | too much access without guardrails. And access here means calling APIs, pulling data, using
00:02:16.620 | tokens, touching credentials, and keys. If that access isn't scoped, monitored, and tied to a
00:02:23.200 | real user, you're wide open to abuse and unintended behavior. And there's other OAuth risks too, like
00:02:29.720 | sensitive data exposure, but that often comes back to the same root cause. If an agent reaches
00:02:34.800 | it into something it shouldn't, it's usually because no one told it not to. And if you've
00:02:40.220 | been listening to the talks here, it's pretty clear that agents aren't just responding to
00:02:43.980 | prompts anymore. They're actually doing real things, calling APIs, kicking off workflows,
00:02:50.040 | changing your systems. And that means they're moving through your stack, touching real user
00:02:54.460 | data, interacting with real external systems. And all needs to happen under the right context,
00:03:01.300 | not just on behalf of your app, but on behalf of your users, individuals. Here's what this
00:03:07.940 | looks like in practice. On the left, you see a pattern that's very common, and an agent calling
00:03:13.760 | an API using a shared key pulled from an environmental variable. That key probably gets reused across
00:03:20.480 | users, environments, maybe even different services. It works, but it's fragile. You can't tell who did what,
00:03:28.260 | and rotating secrets and keys is manual and risky. And the access is way too broad. On the right,
00:03:34.500 | things are looking better. Instead of a shared static key, the agent asks your back end for a token
00:03:40.020 | that's just for the one user and one API. The back end uses something like token exchange to hand out
00:03:45.860 | short-lived token, and the agent can safely use that. Now you've got a real record of who did what,
00:03:52.260 | and you can easily rotate tokens, support multiple upstream systems, and manage all of this in a
00:03:59.300 | centralized way. And we've seen that this approach scales much better, even if you have dozens or
00:04:05.140 | hundreds of agents. It's a small tweak in your flow, but a big step up in how you handle risk.
00:04:10.660 | So we've talked about the secrets and shared credentials problem, but underneath it all is identity. If your agent
00:04:18.260 | doesn't have identity, it doesn't matter how well you scope your tokens or rotate tokens, you still
00:04:23.140 | don't have real control here. And identity is what connects actions to users. It lets you understand what
00:04:28.740 | happened, trace the activity, and decide what should happen next. But before we talk about delegation and
00:04:35.380 | scope, we need to get identity right. So if identity is the part we're often missing,
00:04:41.140 | the next question is, who's the agent actually working for? If you don't have a clear answer,
00:04:45.300 | chances are it's just running as a service account. And that's how you get the confused deputy problem
00:04:50.340 | that you've been hearing a lot about this week. The agent has access, but it doesn't know who it's
00:04:55.060 | acting for, what it should avoid. The fix is to anchor that behavior into real users using standard flows like
00:05:01.940 | OAuth 2.1, RAR, and token exchange to tightly control what the agent can do.
00:05:07.860 | That includes refreshing tokens properly and preserving upstream identity. And here's how it works in
00:05:15.780 | practice. When the agent needs to act, it doesn't grab a key from a static config. Instead, it asks your
00:05:21.940 | backend for help. The backend reaches into a vault, fetches a credential, and uses token exchange to mint a
00:05:29.460 | token that's short-lived just for this user in API. That token never sits with the agent. It's handed
00:05:35.540 | off, used, and gone. And this keeps your agent focused on doing its job, not carrying around secrets.
00:05:41.140 | And it lines up with familiar OAuth standards, so you're not inventing the wheel, what you should never
00:05:45.700 | do with identity. And let's shift gears now to the RAG systems. When you're using RAG, you're not just
00:05:52.500 | handing data to the model, you're making choices about what it sees. And not every user should unlock the same
00:05:58.180 | data context. This is where fine-grained authorization matters. You don't want the agent deciding access
00:06:04.020 | inside your LOM. You want that enforced much earlier at the retrieval layer where policy enforcement can
00:06:11.300 | kick in. So you want to do this so that none of your sensitive data leaks out and you keep your company
00:06:19.380 | and your users secure. Early we covered how to scope access which agent can see, but sometimes that's not
00:06:27.060 | enough. You also need to control when and how that access is provisioned. That's where client-initiated
00:06:33.940 | back-channel authentication, which is a mouthful, or CBA comes in. Instead of showing a UI, the agent sends a
00:06:41.060 | request to the authorization server. The server reaches out to the user on a trusted device via something like
00:06:47.380 | a push notification. And then the user decides whether to approve, deny, or ask for more information.
00:06:53.540 | This pattern is really great for agents running in the background or in scenarios where there's no
00:06:59.540 | screen to direct a user to. And Cam's going to show the demo where this really comes to life. So keep
00:07:04.580 | this flow in the back of your mind when you see that. And of course, we can't talk about secure agent
00:07:10.260 | execution without talking about MCP. MCP servers are showing up everywhere these days. What started as
00:07:17.620 | local standard I/O input on your laptop is quickly turning into a core part of remote distributed systems.
00:07:24.740 | And once you put it on the network, security can't be an afterthought. This pattern here uses
00:07:32.180 | OAuth 2.1 flow. The user signs in through the browser. MCP server handles the token minting behind
00:07:38.580 | the scenes. Agent gets scoped and bound up front. And the token is issued. The client never exposes
00:07:44.180 | third-party credentials. The key takeaway is that when every tool in action runs on a shared server,
00:07:50.180 | the need for solid guarantees about who's making each request and doing what they're actually doing,
00:07:54.580 | becomes critical. All right. So to bring this all together, Cam's going to show what this looks
00:07:59.380 | like in practice. He's going to go through the CBA flow, an example. Cam, over to you.
00:08:03.700 | Thanks, Bobby. A lot of talking points. This is not going to be a challenging demo.
00:08:09.220 | So thanks for outlining some of the security and identity challenges that are facing production-grade
00:08:14.180 | API agent or AI agents. Building on that overview, you can actually find this demo alongside of a ton
00:08:20.020 | of other really great working demos at the following URL that I've got highlighted in the
00:08:24.260 | package.json of this little application. For our scenario, we're going to do a local AI
00:08:29.460 | trading assistant that's going to make local tool calls to a broker service in order to buy stock.
00:08:33.540 | Right? We tried to make this as applicable as possible. The AI agent is going to initiate the
00:08:38.420 | request, but the user is going to have to explicitly approve it before it's executed. I don't have enough
00:08:42.900 | screens. I'm going to pull another one up. This will be really fun. Bear with me. All right. So this is going
00:08:48.020 | to really emphasize three critical components for us. So we're going to have user identification and context,
00:08:53.220 | which is going to identify the user so that the agent can act on their behalf.
00:08:56.260 | We're going to use the token vault to prevent us from embedding any kind of like, you know,
00:09:01.140 | secrets or sensitive data inside of our actual code. And then we're going to use SEBA or client
00:09:05.700 | initiated back-channel authentication to request user consent before the agent executes sensitive
00:09:10.580 | actions. It's worth highlighting, this is going to require an identity provider that supports SEBA.
00:09:14.740 | You know, our Auth0 is the company we work for. But importantly, like SEBA is part of the OIDC
00:09:20.340 | specification. So this is going to become more ubiquitous over time.
00:09:22.900 | So taking a look at the app that's driving this demo.
00:09:25.460 | Zoom out a couple here.
00:09:29.860 | Cattle of IntelliJ. I'm going to hide this too.
00:09:33.300 | There we go. Okay. So we're using a pretty simple TypeScript CLI application.
00:09:38.580 | Anybody speak TypeScript in the room? Show of hands. View. Okay. All right.
00:09:41.940 | All right. The Python demo will come later. So this is basically just going to expose a command line
00:09:48.260 | interface for the user, which is configured with a stock trading tool. Let me make sure
00:09:54.260 | that a couple of prereqs that I need are true. Bear with me one second.
00:10:00.580 | All right. I do not see what I'm looking for.
00:10:09.460 | But that should be fine, hopefully. So rolling back in a more mature trading environment,
00:10:16.260 | it would probably look something like this. So you'll have this user who's going to specify,
00:10:20.420 | you know, buy some stock at a certain threshold, which will get dispensed as a user action
00:10:24.980 | that'll interface with your chat bot. There'd be some sort of polling system that would be capable
00:10:30.020 | of understanding what, like, the real-time value of that stock trade is over time. And then it would,
00:10:34.260 | at the time where whatever threshold is defined, at that point, it would then execute the trade. And
00:10:39.300 | prior to executing the trade, it would dispatch a CBA request to make sure that the user is able to
00:10:43.060 | authenticate that, right, at the last mile. Now, with today's stock volatility being what it is,
00:10:48.180 | it'd be a real challenge to define any reliable trade rule based on vibes and true social posts. So
00:10:52.340 | we're going to elide certain elements of this stack. That was a joke. I hope you enjoyed that.
00:10:56.500 | Stay on the script now. So we're going to take a look at the actual application. So this is the
00:11:02.020 | primary agent configuration and runtime definition inside of this stack. You can see it's bifurcated
00:11:07.380 | into two primary methods. You've got the generate messages method, which is basically the handler
00:11:11.780 | that's interfacing between the LLM and the user. Here we're passing in this buy tool, which we're
00:11:15.860 | going to dig into a little bit more. Then subsequent to that, you have the main method, which is going
00:11:20.660 | to run on the main thread. Here's where we're going to define the context and bind it to a particular
00:11:24.820 | thread for this particular session. This is going to give us the ability to
00:11:28.580 | durably identify who is using this particular session.
00:11:34.340 | So within that, let's take a look at the actual tool call.
00:11:38.420 | So here we have the definition of a tool call instead of this application. You'll note that it's
00:11:45.940 | wrapped in two higher order component functions. So there's this use device flow. Take a look at that.
00:11:51.700 | This is basically how it is that we are bootstrapping the OIDC wrapper within this application.
00:11:59.060 | That's going to enable us to have an auth client bootstrap for the application. This enables me to use
00:12:03.780 | access tokens on behalf of a user, basically. So there's a whole lot of configuration that goes into
00:12:07.620 | that. Stepping back, there's another handler, this use CBA high order component. So this is basically going to
00:12:13.460 | enable this application to intercept the tool call in which we want the user to be able to approve or deny
00:12:20.180 | behavior. And that's really the heart of CBA. It's also where we provide configuration information to our
00:12:24.900 | identity provider about how we're going to use the resources. So in our case, we're using a blocking
00:12:29.140 | callback down here, which is going to actually pause the application while it's running in order to wait for
00:12:36.020 | the approval. You could wire this up with an asynchronous flow or polling or however you wanted
00:12:40.500 | to. But for the sake of this demo, this was a lot easier. One last thing that I'll highlight here,
00:12:45.860 | inside of the buy tool call, the way that we're handling credentials here on line 28, this prevents
00:12:51.140 | us from embedding client secrets directly inside of the code, which is a nice utility method. In Auth0,
00:12:57.300 | we call this our token vault. It's dissimilar from like one password or like a credential manager.
00:13:02.180 | This is managing tokens, not credentials. So just a little bit of a note there. So let's get these
00:13:06.580 | services running. Fingers crossed we don't run into any super fun Wi-Fi issues. May the Wi-Fi gods be
00:13:14.020 | forever on our side. As that comes up, so even with local AI agents, identity matters, especially when
00:13:19.860 | you're using multiple users with multiple agents involved or if the agent has access to, you know,
00:13:25.780 | user data or user files, right? So it's crucial to know who is interacting with what and at what time,
00:13:31.780 | especially like once compliance gets into this, it gets a lot more robust. So we're not using a
00:13:37.300 | fully stateful service to preserve and persist the user's identity. So instead, we're going to
00:13:41.460 | authenticate the users through the UI. So you'll notice that a little blurb will pop up. After that,
00:13:45.460 | when the AI agent needs to perform a sensitive action, like executing a trade, we're going to rely on
00:13:49.220 | SEBA. That means no additional user-facing UI is required after the user has given us their tokens.
00:13:54.740 | And I'll give you a little bit of a demo of that. So we're definitely aware that identity standards like
00:13:59.140 | OAuth 2, OIDC, SEBA, these are not always top of mind or super interesting for developers when
00:14:03.700 | you're building great applications. That's why we're highlighting that there are solutions to
00:14:06.900 | these problems. There's open standards that are evolving over time. And a lot of those are live
00:14:11.220 | today. So that can be something to be thought of in the progenation of an application instead of as an
00:14:16.500 | afterthought. So one thing that we run into repeatedly while building this demo is determining just how
00:14:20.660 | much complexity is needed to effectively showcase these sorts of scenarios. Please let us know how
00:14:27.220 | we struck that balance. You can email Bobby directly. He loves feedback. All right. So now for the fun
00:14:33.140 | part, let's try and buy some stocks with this. If the Wi-Fi gods are on our side. So buy me 10 stocks
00:14:39.220 | as well. So what this is going to do is the LLM is going to process the intent and it's going to select
00:14:47.540 | the buy stock tool. The agent is going to send... I love Wi-Fi. It's my favorite thing. Let's see if
00:14:54.900 | there isn't a way around... I can just sort of talk through this at a high level. We'll take a look at
00:15:01.060 | this piece of our demo. So what would have, you know, ideally happened is we would execute that trade
00:15:10.420 | because we have bootstrapped our middleware inside of this application. It would prompt me to log in. And
00:15:14.260 | once I logged in, then the agent would have tokens to use on my behalf as a user. That would then execute
00:15:19.140 | a trade. It's not actually executing a trade. I have like a local trade service running. So you're not
00:15:22.660 | missing many highlights. Don't worry. And then once the trade, or once I authenticated, then CBA would
00:15:29.700 | immediately dispatch a notification to Auth0's backend and it would blow up my phone and I would
00:15:34.020 | get a little notification that's like, "Hey, would you like to approve and authorize this trade?" One
00:15:38.900 | nice thing about the notification that I get is it's not just going to tell me, "Hey, something happened.
00:15:42.980 | Would you like to approve it?" And I'm just going to be left generically in the middle of the ether trying
00:15:46.580 | to figure out where this notification came from. It's actually going to say, "This agent tried to do this
00:15:51.220 | action," which in this case is purchasing 10 shares of something. "On your behalf, would you like to
00:15:55.300 | approve that downstream flow?" And then I have the autonomy as an end user to determine whether or
00:16:00.740 | not I'm going to advance that flow or cancel that flow. One other thing that I would have shown is
00:16:05.220 | once I've authenticated the agent to act on my behalf, subsequent trades no longer need me in the loop
00:16:10.100 | outside of the CBA flow. So once I've authenticated in, the authorization flow is then just dispatched
00:16:16.420 | to CBA, which is pretty cool. So going back to my notes, let me see how many things that I missed.
00:16:22.420 | A bunch of things, but that's okay. So in summary, we saw, well, kind of, I talked through how an agent
00:16:28.580 | can identify and act on behalf of a specific user, how to store and access credentials securely, and how to
00:16:33.940 | use CBA to get explicit user approval for high-risk actions like trading without a browser to be
00:16:39.540 | required. So these patterns are live. They exist in defined standards like OIDC, CBA, rich authorization
00:16:45.860 | requests, Auth0 developer previews have, like, attempts at a lot of these things or actual production
00:16:51.780 | versions of a lot of these things. So I'd encourage you guys to check some of those things out. And some of
00:16:55.700 | these are GA, like our CBA, I believe is GA, or at least in early access, instead of our platform. So
00:17:00.740 | that's pretty cool. Thanks. That's my demo. All right. So just moving back here, go to the next slide.
00:17:07.700 | Cool. But as Cam was saying, like, we're trying to make this easier for developers. The standards are
00:17:16.180 | great. It means you're not reinventing things over and over again. But there's still a lot of building
00:17:21.380 | blocks that you would have to build. The Auth0, other platforms have similar things. But we're
00:17:26.980 | trying to wrap this all up in a nice experience for developers. So the async user confirmation with CBA
00:17:32.980 | is, like, a very key component here, as well as calling APIs on BAP users, things like token exchange
00:17:39.380 | and token vault, as well as fine-grained authorization for RAG. You don't have to use our platform for this.
00:17:45.060 | We have OpenFGA that can snap into this type of thing as well. And user authentication and
00:17:52.260 | authorization generally is something that we do. We think deeply about. And with that,
00:17:57.860 | here's what you can take with you. It's a nice QR code. And we'd love to talk to anyone who's
00:18:05.780 | struggling with these challenges, what you've hit in your day-to-day life. Just come talk to us after the
00:18:13.220 | talk. I'm also part of the open MCP spec, driving some of those discussions as well. We're very
00:18:21.460 | interested in improving that for everybody. So it's not just about Auth0, working with other open source
00:18:27.940 | libraries like MCP auth, which is another great thing. So very deeply interested in this space.
00:18:33.220 | Thanks. Thank you.
00:18:35.380 | Thanks.
00:18:37.380 | Thank you.