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.
Redirect URI and everything.
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?
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.
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?
I love this question because it feels like it should have an obvious answer, and it absolutely does not.
That's never stopped you before.
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."
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.
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.
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.
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.
So walk me through the standard flow. I open Claude, it needs to prove I'm me. What actually happens?
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.
And the browser already has a Google session cookie sitting there.
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.
So even when Google knows it's you, the protocol demands that the code physically traverse the browser.
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."
And then what happens to that code?
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.
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.
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.
So Claude could store the token wherever it wants. It's not trapped in the browser.
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.
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."
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.
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.
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.
So the dance isn't bureaucracy. It's a containment system.
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.
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.
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.
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?
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.
So we've established why the browser is central. What about the storage side? Daniel specifically asked about Linux. What does Linux actually offer?
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.
And then there's the userspace side.
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.
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.
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.
So the storage isn't the problem. It's the lifecycle management.
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.
Which means what Daniel actually needs isn't a better keychain — it's a token broker.
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.
But that only works for GNOME apps.
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.
And Claude is an Electron app. It's not going to integrate with GOA.
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.
What about the cloud CLI tools? They seem to have figured this out.
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.
So it's a token broker, just scoped to Google's ecosystem.
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.
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.
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.
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.
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.
Though if malware has code execution on your machine, you've already lost. It can just keylog your next OAuth dance anyway.
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.
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?"
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.
The browser is simultaneously the only place that knows you're authenticated and the place that's forbidden from sharing that information.
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.
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?
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.
But the provider has to support Token Exchange.
Adoption is minimal. Google supports it in some contexts, Microsoft in others, but it's not universal. Most OAuth providers don't implement it.
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.
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.
Like polkit but for OAuth.
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.
Then every app on the system benefits from that single authentication.
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.
This sounds like what Daniel was actually asking for. Apps query the local store before going to the browser.
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.
Nobody wants to maintain that daemon because the integration surface is enormous.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The storage isn't the bottleneck. It's the logic that sits on top of the storage.
Hilbert: Always is.
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.
The token lifecycle. Expiry, refresh, revocation, scope changes.
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.
Unless you have a token broker that can do Token Exchange.
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.
Where does this leave Daniel and his printer?
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.
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.
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.
But we're not there yet. And even when we get there, someone still has to maintain the integrations.
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.
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.
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.
But the protocol doesn't know that. And it can't know that. "Trusted environment" is not a claim the protocol can verify.
That's the fundamental tension. Security protocols are designed for the worst case. Convenience features are designed for the common case. OAuth chose security.
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.
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.
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.
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.
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.
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.
We'll be back soon.