#4733: Why OAuth Still Makes You Dance on Your Own Machine

Why does OAuth treat your personal computer like a public terminal? We explore token storage, PKCE, and Linux secret APIs.

Featuring
Listen
0:00
0:00
Episode Details
Episode ID
MWP-4912
Published
Duration
28:21
Audio
Direct link
Pipeline
V5
TTS Engine
chatterbox-regular
Script Writing Agent
deepseek-v4-pro

AI-Generated Content: This podcast is created using AI personas. Please verify any important information independently.

Daniel's morning routine hit a familiar wall: he needed to update a printer setting in a Windows VM, but first he had to install Claude, which meant authenticating through a browser that was already signed into Google. The session was right there. The VM knew who he was. And Claude still demanded the full OAuth redirect dance.

This friction raises a deeper question: is OAuth 2.0's flow fundamentally designed for a threat model that doesn't match how we actually use our own computers? The protocol, standardized in 2012, was built for a world where web apps needed to delegate authorization between servers without sharing passwords. The browser became the natural intermediary — it's the one place where you, the human, can directly interact with the identity provider. The application never sees your password or touches your session cookie. It only ever receives the authorization code after you've clicked "allow."

But that design assumes every auth event needs a fresh user gesture, treating a trusted personal machine the same as a public library terminal. PKCE adds another layer of containment: each app gets its own token, its own scope, and its own lifecycle. If you revoke Claude's access, nothing else breaks. If a token gets leaked, the damage is limited to what that specific app was authorized to do. That blast radius management is intentional — supply chain attacks and compromised dependencies are real threats, even on trusted machines.

On the storage side, Linux has had proper secret infrastructure for decades. The kernel keyring (since 2004) and the freedesktop.org Secret Service API (used by GNOME Keyring, KDE Wallet, and KeePassXC) can hold secrets. But OAuth tokens are fundamentally different from static secrets like SSH passphrases. They expire, get refreshed, and are bound to specific scopes and audiences. What Daniel actually needs isn't a better keychain — it's a token broker that manages the full lifecycle. GNOME Online Accounts and KDE's KAccounts do this, but only within their own desktop environments. For Electron apps like Claude, or CLI tools, there's no universal solution that would make the redirect dance optional.

Downloads

Episode Audio

Download the full episode as an MP3 file

Download MP3
Transcript (TXT)

Plain text transcript file

Transcript (PDF)

Formatted PDF with styling

#4733: Why OAuth Still Makes You Dance on Your Own Machine

Corn
Daniel spent his morning trying to update a printer setting on a Brother printer through a Windows VM he fires up about twice a year. And before he could even get to the printer, he had to install Claude, which meant authenticating Claude through Windows, which meant watching his browser open a Google sign-in page even though the browser was already signed into Google. The session was right there. The VM knew who he was. And Claude still had to do the full OAuth two-step.
Herman
Redirect URI and everything.
Corn
The whole dance. So here's what he's asking. He's in a trusted environment — his own machine, his own VM, full disk encryption, nobody else touching it. He's using Google as a federated identity provider, and the browser already has a persistent authenticated session. Why does Claude need to go through the OAuth flow again? And more specifically, is there a way on Linux or other operating systems to maintain a secure local store of these generated tokens so that apps can query them directly before bothering with the browser?
Herman
He's essentially asking whether we can short-circuit the redirect dance when the trust is already established. And whether the OS itself could be the token custodian instead of the browser.
Corn
So today we're asking — is the OAuth flow fundamentally designed for a threat model that doesn't match how we actually use our own computers?
Herman
I love this question because it feels like it should have an obvious answer, and it absolutely does not.
Corn
That's never stopped you before.
Herman
It has not. So let's lay out the actual tension here. OAuth two point zero was standardized in RFC six seven four nine back in October twenty twelve. It was designed for a world of web apps delegating authorization between servers. You've got a client application, a resource server, and an authorization server. The browser was the natural intermediary because the whole problem was "how does this web app get access to my Google Drive without me handing over my Google password."
Corn
Right. The original sin is that we took a protocol designed for server-to-server delegation through a browser and then bolted it onto desktop apps, CLI tools, and system services where the browser is this awkward middleman that doesn't belong.
Herman
And Daniel's scenario captures exactly why. He's got three specific inefficiencies baked into that one printer adventure. One, the redirect dance happens even when the browser is already authenticated with Google. Two, the tokens get generated locally but end up stored in the browser's storage rather than somewhere system-wide that any app could reach. And three, the whole flow assumes every auth event needs a fresh user gesture even when the environment is fully trusted.
Corn
That third one is the one that gets me. The protocol treats Daniel's dusty Windows VM the same way it treats a public library terminal. Same threat model, same ceremony.
Herman
And that's where we're going today. First we need to understand why the flow works this way — the threat model, what PKCE actually protects against, and why the browser is non-negotiable in the spec. Then we'll look at what Linux actually offers for local token storage — the secret service API, kernel keyrings, the whole stack. And finally we ask whether a trusted-environment shortcut is even desirable, or whether the token lifecycle management problem makes it harder than it looks.
Corn
So walk me through the standard flow. I open Claude, it needs to prove I'm me. What actually happens?
Herman
Step by step. The app generates an authorization request. It says "I need access to these scopes, here's my client ID, and here's a randomly generated code verifier." That code verifier is the PKCE part, we'll come back to it. The app then opens your browser to the identity provider's authorize endpoint — something like accounts dot google dot com slash o slash auth.
Corn
And the browser already has a Google session cookie sitting there.
Herman
It does. The identity provider might recognize that cookie and skip the username and password step. You might just see a consent screen. One click. But the redirect still happens. The browser still has to receive the authorization code through the redirect URI, and that code still has to be handed back to the application.
Corn
So even when Google knows it's you, the protocol demands that the code physically traverse the browser.
Herman
Because the browser is the user agent. It's the one place where you, the human, can directly interact with the identity provider. The application never sees your password. It never touches your session cookie. It only ever gets the authorization code, and only after you've clicked "allow."
Corn
And then what happens to that code?
Herman
The app exchanges it for tokens. It sends the authorization code plus that code verifier to the token endpoint. The authorization server checks that the code verifier matches the code challenge it received earlier — that's PKCE, Proof Key for Code Exchange, RFC seven six three six from twenty fifteen. If they match, the server issues an access token and usually a refresh token.
Corn
And this is the part where Daniel's question bites. Those tokens were generated locally. The browser and the app are on the same machine. But the token ends up living in browser storage.
Herman
Well, it depends on the type of app. For a web app, yes — the OAuth flow terminates in the browser, and the tokens typically end up in localStorage or IndexedDB. But Claude is a native app, or at least an Electron app. Native apps use a different mechanism. RFC eight two five two from twenty seventeen recommends using either a custom URI scheme or a loopback redirect. The app spins up a tiny local web server on a random port, the browser redirects to localhost, and the app grabs the authorization code directly from the URL.
Corn
So Claude could store the token wherever it wants. It's not trapped in the browser.
Herman
It's not trapped in the browser. But most apps store it in their own sandboxed storage. On Linux, that's somewhere under dot config or dot local share. On Windows, it's in the app data directory. The token is local, but it's local to that specific app.
Corn
Which means if I install a second app that also wants to talk to Google, it can't just reach into Claude's token store and say "hey, he's already authenticated, give me the token."
Herman
Correct. And that's by design. OAuth tokens are issued to a specific client. The access token has an audience claim that says "this token is for Claude, not for some other app." Even if you could read Claude's token store, the token wouldn't be valid for a different client.
Corn
So the isolation is a feature. But it's also the source of the friction. Every app has to do its own dance because every app gets its own token.
Herman
And that's where the threat model comes in. The OAuth spec assumes the client application might be compromised. If Claude has a vulnerability, that compromised app should not be able to impersonate the user to other services. By routing everything through the browser, the authorization code never touches the app until after you've explicitly consented. PKCE adds another layer — even if a malicious app intercepts the authorization code, it can't exchange it for tokens without the code verifier.
Corn
So the dance isn't bureaucracy. It's a containment system.
Herman
It's blast radius management. Each app gets its own token, its own scope, its own lifecycle. If you revoke Claude's access, it doesn't affect anything else. If a token gets leaked, the damage is limited to what that specific token was authorized to do.
Corn
But Daniel's point still stands. In a trusted environment — single user, full disk encryption, no malware — the containment is protecting against threats that don't exist.
Herman
The threats do exist though. Supply chain attacks are the obvious one. A compromised NPM package or a poisoned Python dependency could give an attacker code execution inside an otherwise trusted app. If that app had access to a system-wide token store that granted broad permissions, the attacker gets everything. The OAuth model limits the damage to whatever scopes that specific app requested.
Corn
Fair. But there's a middle ground between "every app fends for itself" and "one token to rule them all." What about proof of possession tokens?
Herman
RFC eight seven zero five from twenty twenty. The idea is that the token is bound to a specific cryptographic key held by the client. Even if someone steals the token string, they can't use it without the corresponding private key. It's a real improvement. But it doesn't change the initial authorization flow — you still need the browser for that first user consent.
Corn
So we've established why the browser is central. What about the storage side? Daniel specifically asked about Linux. What does Linux actually offer?
Herman
This is where it gets interesting. Linux has had a proper secret storage infrastructure for twenty years. The kernel keyring has been available since kernel two point six point ten in two thousand four. It's a kernel-level key-value store that can hold arbitrary blobs — encryption keys, passwords, certificates. You interact with it through the keyctl syscall, and keys can be tied to specific users, sessions, or processes.
Corn
And then there's the userspace side.
Herman
The freedesktop dot org Secret Service API. This is the standard interface implemented by GNOME Keyring, KDE Wallet, and KeePassXC. It's a D-Bus service that apps can query for secrets. You unlock your keyring once when you log in, and after that any app can ask for stored credentials — provided it has the right D-Bus permissions.
Corn
So in theory, Claude could store its OAuth tokens in the GNOME Keyring, and then some other app could query the keyring and find them.
Herman
In theory. But there are several problems. The first is that the Secret Service API is designed for static secrets — your SSH key passphrase, your database password, your Wi-Fi pre-shared key. These are things that don't change. OAuth tokens are fundamentally different. They expire. They get refreshed. They're bound to specific scopes and audiences. Storing an OAuth token in the keyring is like storing milk in the pantry — it'll go bad and the pantry won't tell you.
Corn
So the storage isn't the problem. It's the lifecycle management.
Herman
An access token typically lives for an hour. A refresh token might live for months, but it can be revoked at any time. The token store needs to understand when a token is about to expire, how to use the refresh token to get a new one, and what to do if the refresh fails. A key-value store can't do any of that.
Corn
Which means what Daniel actually needs isn't a better keychain — it's a token broker.
Herman
A system daemon that manages OAuth tokens on behalf of multiple apps. And these exist. GNOME Online Accounts does exactly this for GNOME apps. Evolution, Nautilus, the calendar, the contacts app — they all get their Google and Microsoft tokens through GOA. You authenticate once, and GOA stores the refresh token and handles the refresh cycle for every app that's plugged into it.
Corn
But that only works for GNOME apps.
Herman
And KDE has KAccounts which does the same thing for KDE apps. And that's the fragmentation problem. Every desktop environment has its own token broker, and none of them talk to each other. If you're running a KDE app on a GNOME desktop, it doesn't know about GOA. If you're running a CLI tool, it doesn't know about either.
Corn
And Claude is an Electron app. It's not going to integrate with GOA.
Herman
Probably not. There have been attempts to build desktop-environment-agnostic token brokers, but none have achieved the kind of universal adoption that would make Daniel's scenario work.
Corn
What about the cloud CLI tools? They seem to have figured this out.
Herman
They have, and they're the best working examples we've got. Take gcloud auth application-default login. When you run that command, it opens your browser, you do the OAuth dance once, and it stores a refresh token at tilde slash dot config slash gcloud slash application underscore default underscore credentials dot json. After that, any gcloud command, any client library using the Google auth library, any tool that respects the application default credentials pattern — they all use that same token.
Corn
So it's a token broker, just scoped to Google's ecosystem.
Herman
And the AWS CLI has a similar pattern with its credential provider chain. It checks environment variables first, then the shared credentials file, then the instance metadata service if you're on EC2. Multiple apps can share the same credential file. But again, it's provider-specific. Your Google token doesn't help your AWS app, and neither of them helps Claude.
Corn
So the real gap isn't technical capability — it's that every OAuth provider has a slightly different flow, and nobody wants to maintain a universal broker that handles all of them.
Herman
That's the maintenance nightmare. Google's token endpoint works one way. Microsoft's works another. GitHub's device flow is completely different from the authorization code flow. A universal token broker would need to understand the refresh semantics for dozens of providers, and those semantics change over time.
Corn
We haven't even touched on the security implications of a system-wide token store. If malware gets access to that store, it gets tokens for everything.
Herman
Right. The OAuth model's fragmentation is annoying, but it's also a security boundary. Each app's token is isolated. A system-wide broker creates a single point of compromise.
Corn
Though if malware has code execution on your machine, you've already lost. It can just keylog your next OAuth dance anyway.
Herman
That's true. The threat model distinction between "malware on the machine" and "malware on the machine that can read a token store" is pretty thin. But the isolation does protect against a more subtle attack — a compromised dependency in one app that tries to exfiltrate tokens belonging to other apps.
Corn
Let me pull us back to Daniel's specific scenario. He's got a Windows VM with a persistent Google session in the browser. Claude opens the browser to do OAuth. The browser already has the Google cookie. Why can't Claude just ask the browser "hey, is this user already authenticated with Google?"
Herman
Because browsers don't expose that. And for good reason. If any app could query the browser's session store, you'd have a privacy nightmare. Every website you're logged into would be visible to every app on your system. The browser's cookie jar is one of the most sensitive pieces of data on the machine.
Corn
The browser is simultaneously the only place that knows you're authenticated and the place that's forbidden from sharing that information.
Herman
Yes. The browser is the user agent. It represents you, not the application. It will show you the consent screen. It will let you click "allow." But it won't let an app bypass that step by peeking at your cookies.
Corn
Which brings us to the interesting question. Is there a way to build a "trusted mode" that shortcuts the flow without breaking the security model?
Herman
Some projects are trying. The Token Exchange pattern from RFC eight six nine three is one approach. It lets a trusted token broker exchange one token for another without user interaction. So you'd authenticate once with the broker, and then when an app needs a token for a specific scope, the broker uses Token Exchange to get a downstream token from the provider.
Corn
But the provider has to support Token Exchange.
Herman
Adoption is minimal. Google supports it in some contexts, Microsoft in others, but it's not universal. Most OAuth providers don't implement it.
Corn
We're stuck. The infrastructure for local token management exists on Linux — the kernel keyring, the Secret Service API, libsecret — but it's designed for static secrets, not OAuth tokens with their expiry and refresh logic. The token brokers that do understand OAuth are either desktop-environment-specific or provider-specific. And the browser, which is the one place that knows you're already authenticated, can't share that information without creating a gaping security hole.
Herman
That's the state of play. But I think there's a path forward, and it's not about bypassing the browser. It's about building a proper system-level authentication agent.
Corn
Like polkit but for OAuth.
Herman
Polkit handles authorization decisions for system actions — mounting a disk, changing the network configuration. An app asks polkit "can this user do this thing," polkit checks its policy, and if needed it prompts the user for a password. An OAuth agent would do the same thing for web service authentication. An app asks the agent "can I get a token for Google Drive with these scopes," the agent checks if it already has a valid token, and if not it opens the browser for the consent flow — once.
Corn
Then every app on the system benefits from that single authentication.
Herman
Right. The agent stores the refresh token, handles the refresh cycle, and serves access tokens to any app that requests them with the right scopes. If an app asks for scopes the user hasn't consented to yet, the agent pops up a consent dialog or opens the browser for a targeted scope escalation.
Corn
This sounds like what Daniel was actually asking for. Apps query the local store before going to the browser.
Herman
It is. And the pieces to build this exist. The kernel keyring or the Secret Service API for storage. D-Bus for inter-process communication. libsecret for a higher-level API. The missing piece is the daemon that understands OAuth semantics — token introspection, refresh logic, scope negotiation, provider-specific quirks.
Corn
Nobody wants to maintain that daemon because the integration surface is enormous.
Herman
Every OAuth provider is a special snowflake. Google uses one token endpoint format, GitHub uses another, Microsoft adds its own extensions. You'd need a plugin architecture where each provider has its own module, and someone has to maintain those modules as the providers change their APIs.
Corn
Which is why the cloud CLI tools are the closest we've gotten. Google maintains the Google module. Amazon maintains the Amazon module. Nobody maintains the universal module.
Herman
That's where things get really interesting — because someone in this room has actually tried to build this.

Hilbert: Twenty nineteen. Factory floor in Ohio. Four hundred headless Linux terminals, no displays, no browsers, no nothing. They needed to authenticate with a cloud ERP system that used OAuth with Microsoft Azure AD.
Corn
No browser at all.

Hilbert: No browser. The terminals were bolted to the walls. Operators scanned badges and the terminal had to pull their work orders from the cloud. I built a token broker that ran on a central server. It used the kernel keyring to store refresh tokens, keyctl to manage access, and a patched version of libcurl that knew how to ask the broker for a token instead of doing its own OAuth flow.
Herman
How did you handle the initial authentication?

Hilbert: Pre-provisioned refresh tokens. We did the browser dance once on a provisioning machine, captured the refresh token, and loaded it into the broker. After that, the broker handled the refresh cycle. The terminals never saw a browser. They just asked the broker for an access token over D-Bus.
Corn
And this worked?

Hilbert: It worked. For eleven years. Well, eleven years so far. The factory's still running. The biggest problem wasn't security. It was that tokens expired at unpredictable times and the apps didn't know how to handle it. I had one terminal app that would crash if the token expired mid-request. The fix was to pre-fetch tokens every fifteen minutes whether they were needed or not.
Herman
The broker was babysitting the tokens.

Hilbert: That's not a security protocol, that's a babysitting service. The broker would wake up every fifteen minutes, check all its tokens, refresh the ones that were close to expiry, and cache the new ones. If a refresh failed, it logged it and kept serving the old token until it actually expired, which gave us a window to fix whatever was wrong with Azure.
Corn
This was all custom code?

Hilbert: About three thousand lines of C and a lot of shell scripts. The D-Bus interface was the cleanest part. The ugly part was the refresh logic. Azure's token endpoint would sometimes return a new refresh token with the new access token, and sometimes it wouldn't. The spec says it can do either. So the broker had to handle both cases, and it had to know which version of the Azure API it was talking to.
Herman
That's exactly the maintenance problem. Every provider behaves slightly differently.

Hilbert: Nobody wants to maintain four hundred integrations. We only had to support one provider, Azure. If we'd had to support Google and GitHub and Okta and everyone else, we'd still be writing it.
Corn
Did you ever try to generalize it?

Hilbert: I wrote up the design and sent it to a few people. Nobody bit. The problem is that every OAuth provider thinks their flow is the standard one, and none of them are quite right. The RFCs describe the happy path, but the real world is full of edge cases. Token revocation, scope changes, rate limiting, weird error codes. A universal broker has to handle all of that for every provider.
Herman
That's why we don't have one. Not because it's technically impossible, but because the integration surface is a nightmare.

Hilbert: The kernel keyring part was the easy part. keyctl add user oauth hyphen token whatever at rate. Done. The hard part was everything around it.
Corn
The storage isn't the bottleneck. It's the logic that sits on top of the storage.

Hilbert: Always is.
Herman
Hilbert's story actually clarifies something important. When Daniel asks about a "secure local store of generated tokens," he's asking about storage. But the storage problem is solved. The Secret Service API can store OAuth tokens. The kernel keyring can store OAuth tokens. A JSON file in a dot directory with restrictive permissions can store OAuth tokens. The problem is what happens after you store them.
Corn
The token lifecycle. Expiry, refresh, revocation, scope changes.
Herman
The fact that every app needs its own token with its own scopes. Even if you had a system-wide token store, you can't just hand Claude's token to some other app. The token is audience-restricted. The other app needs its own token, which means its own authorization code flow, which means the browser.
Corn
Unless you have a token broker that can do Token Exchange.
Herman
Which brings us back to the adoption problem. Token Exchange is the right technical solution — a trusted broker gets a master refresh token, and when an app needs access, the broker uses Token Exchange to get a downstream token with the right scopes and audience. No browser required after the initial setup. But the providers have to support it, and most don't.
Corn
Where does this leave Daniel and his printer?
Herman
In the short term, he's stuck with the dance. The OAuth flow is doing exactly what it was designed to do — it's containing the blast radius of each app's authentication and ensuring that the user explicitly consents to each app's access. The fact that it's annoying on a trusted machine is a side effect, not a bug.
Corn
But the long term is more interesting. We're moving toward passkeys and platform authenticators. WebAuthn, platform credentials stored in the TPM or the secure enclave. The question is whether the browser remains the central authentication hub, or whether the OS takes over that role.
Herman
If the OS becomes the authentication hub — if Windows Hello or the Linux equivalent manages your WebAuthn credentials and your OAuth tokens — then the browser dance starts to look like a transitional phase. The OS knows who you are because you unlocked it with your fingerprint or your face. It can attest to your identity without asking you to re-authenticate. And it can serve tokens to apps without ever opening a browser.
Corn
But we're not there yet. And even when we get there, someone still has to maintain the integrations.
Herman
The passkey model actually simplifies this. With passkeys, the credential is bound to the device, not to a specific app or browser. The OS authenticator can sign challenges for any relying party. It's not quite the same as a token broker, but it points in the same direction — the OS as the root of trust, not the browser.
Corn
Maybe the answer to Daniel's question is that the infrastructure exists, the storage exists, the kernel primitives exist — but the integration layer that would make it all work seamlessly is a maintenance problem nobody has been willing to fund. And until someone does, we're all doing the OAuth dance, even on machines that already know exactly who we are.
Herman
The one thing I'd add is that the inefficiency Daniel's feeling isn't really about the number of clicks. It's about the mismatch between the protocol's assumptions and his actual threat model. OAuth assumes the network is hostile and the client might be compromised. Daniel's sitting at a machine he owns, behind a locked door, with full disk encryption. The protocol is protecting him from threats that are, in his specific context, extremely unlikely.
Corn
But the protocol doesn't know that. And it can't know that. "Trusted environment" is not a claim the protocol can verify.
Herman
That's the fundamental tension. Security protocols are designed for the worst case. Convenience features are designed for the common case. OAuth chose security.
Corn
The cutting room floor detail I wanted to mention — KeePassXC actually has a browser integration that's closer to what Daniel wants than most people realize. It runs a local WebSocket server, and the browser extension talks to it to fetch credentials. It's not OAuth, but the architecture is exactly what a token broker would look like. Local daemon, encrypted storage, apps query it over a local connection. The pieces are all there, they're just being used for passwords instead of tokens.
Herman
Which raises the bigger question. If we can build a local credential broker for passwords, why not for OAuth tokens? And I think Hilbert's story is the answer. Passwords are simple — they're static strings. OAuth tokens have a lifecycle. The complexity isn't in the storage, it's in the refresh logic, and every provider does it differently.
Corn
We'll leave it there. One forward-looking thought — as passkeys continue rolling out and platform authenticators become the norm, the browser's role as the authentication intermediary is going to shrink. The question is whether the OS steps into that role, or whether we end up with yet another fragmented ecosystem where every app does its own thing.
Herman
Daniel's grandchildren will either inherit a world where authentication is seamless, or they'll still be opening browsers to click "allow" on consent screens. My money's on somewhere in between.
Corn
This has been My Weird Prompts. Thanks to our producer Hilbert Flumingtop for keeping the show running and for occasionally reminding us that the problems we theorize about are problems he's already solved in a factory in Ohio.
Herman
If you've got a weird prompt about authentication, printers, or the hidden complexity of things that should be simple, send it to show at my weird prompts dot com. We read every one.
Corn
We'll be back soon.

This episode was generated with AI assistance. Hosts Herman and Corn are AI personalities.