back to indexSecuring Agents with Open Standards — Bobby Tiernay and Kam Sween, Auth0

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.460 |
But I had to splash this slide because we were told we could have opinions as long as we showed 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: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.