Bearer tokens are just awful
mjg59.dreamwidth.org
mjg59.dreamwidth.org
I'm not disagreeing with your underlying point, really. I do agree that authentication is broken in that it fails to provide the actual guarantees we think it does. This is what I was getting at with my point above about issuing a right to talk to my service--that is, in reality, all any practical authentication system can provide today. You simply can't be certain the TPM your service stashed it's keys is secure without forcing attestation (again, problematic). Therefore, as far as your service can assume, your client may have just published the keys for the world to see. This leaves you in no better a theoretical position and it does not solve any of the problems identified with any other auth mechanism purely because it is not fundamentally.
> What if we have a scenario where a third party authenticates the client (by verifying that they have a valid token issued by their ID provider) and then uses that to issue their own token that's much longer lived?
He might as well be complaining about car keys. Yes, if I leave my car key in the door of my car, bad things will happen. If you issue a long lived bearer token, bad things will happen.
Bearer tokens are credentials. Short lived and limited, but still credentials. You have to take care of credentials!
Use short lived bearer tokens and refresh tokens. Threat model the ramifications of someone stealing the token. If you really higher assurances, look into client bound bearer tokens (DPoP and MTLS in the OAuth world.)
For a common use case (I work for an auth provider, more details in my bio) of browsers or native applications integrating with other applications and APIs, what are other options for verifying a client is authorized to access a resource:
* sessions, probably with cookies. Well known, solid technology. Requires you to either have sticky sessions (so every client request goes to the same server) or a common session store (redis, etc).
* client certificates. If you control all deployment (intranet, employee laptops, etc) can be an option. https://buoyant.io/mtls-guide/ is a good resource if you are doing service to service communication.
* API keys. How do you like your credentials with no internal data structure. Plus, they live forever until you build a rotation system. Yay!
* client bound tokens as mentioned above
I'm not sure what other options are available.
Edit: formatting
I can issue a short lived bearer token, and another service I don't control can then decide based on that to issue a long lived one and I have no generic way to gain insight into that.
But that's a bit like saying:
I have a metal key that I only give to trusted people, but someone I trust can make a copy of that key and give me back the original. Therefore I can't trust metal keys.
I guess I'd want to dig in a bit more to understand the use case. Who is deciding to use the other service? Who is accepting the long lived token? (you? If so, why? The issuer? Well then, they made that choice.)
Here's an example scenario. One of my users runs Github Desktop and clicks "Sign in". This process involves them performing extra authentication in order to gain access to our enterprise organisation, which is handled by my identity provider. I can hook into that authentication process in order to verify device identity and state, and I can issue a short-lived token. Github will then happily take this short-lived token and provide a long-lived token to the Github Desktop app which will grant access to my organisation's source code without any further authentication, and which is not bound to the device in any way.
I'm actually curious about the legal situation of stuff like this. Specifically,
1. You issue me a token,
2. that token gets stolen,
3. the stolen token is used to run up a big bill on your service,
4. I refuse to pay that bill (the case being "my laptop was stolen and your service was stolen -- sucks to by both of us, but it's not my obligation to reimburse you for that theft"),
5. You sue me.
What happens? I'm actually genuinely curious, with respect to "stolen services": whose problem is it, really?
What if we insert a new step between 3 and 4 where I tell you the token was stolen, but you choose to accept the token anyways (because eg there wasn't an automated process and the ticket takes a few hours to be resolved)?
(The "you" hear is of course meant to be generic, not Sirened specifically :))
But generally you will be liable for the costs incurred from items stolen from your custody. You must then recover those costs from whoever stolen from you
Let's assume not.
> or do you have any contractual limitations on cost that may protect you?
I guess in this hypothetical case the person whose laptop was stolen cancels the credit card before the charge is made.
> But generally you will be liable for the costs incurred from items stolen from your custody.
Right... but who was the victim of theft here? The service provider or the client of the service provider?
> You must then recover those costs from whoever stolen from you
So... can the service provider sue you to pay for unauthorized use of your account? Or would that get thrown out and then they would have to go sue the person who actually stole the service?
I think it's clear to me that once the service provide gets cash from the person whose laptop was stolen, it's the problem of the person whose laptop was stolen. But if not, is the problem of the service provider?
If you refuse to pay and they sue you then they will win and a court will force you to pay, even if it means a sheriff comes to your house and takes your things to sell at a public auction to come up with the money.
You will have to recover from the thief if you want to be made whole. It's possible that if you know who the thief is and can serve them, you can join them to the suit the provider brings against you. Then the court will adjudicate the whole thing together and the thief will directly pay the provider. But that still starts with suing you and action on your part to find and sue the thief.
Bearer tokens are no different to a password in that way. Netflix is currently battling shared passwords, and people regularly have their passwords stolen by a site impersonating the other end.
Invalidation is only useful if you know the bearer token has been stolen or compromised so it doesn't solve them problem. It's no different to demanding your users change their passwords after a leak has been publicised. And besides - it's already possible to keep a registry of invalid tokens, just a it's possible to set a "password must be changed at next login" flag, so invalidation is possible now.
But - the article seems to totally ignore the improvement short lived tokens makes to the situation. If the token only live for 15 minutes, the damage it can do is presumably limited. Still, the point remains - if you could replace a bearer token with something that did mutual authentication on every exchange it be much more secure.
Conceptually, it's not even that difficult. IPSec effectively does it now for every packet sent. You "just" need to build IPSec like mechanisms into every exchange. Actually it's not that hard - with a standardised protocol app developers could use it without much change. But there doesn't appear to be much movement in that direction.
Somewhat harder is "mutually authenticate with what". If the threat model is "someone hacks your computer, and steals the credentials", then it has to be tied to something unhackable that needs to be physically stolen to get any improvement. IPSec doesn't address the problem. Sure, you can authenticate against a certificate, but unless someone has going to the trouble of using a HSM that certificate is really just another long lived bearer token that can be stolen.
That can be fixed. One can imagine a person using a FIDO2 key to authenticate themselves, but all the FIDO2 key does is authorised the TPM in their device to act on their behalf for a while, and the TPM establishes a trust relationship with the servers HSM and somehow tying that all together so every packet exchanges is authenticated with that trust relationship. But now we are talking real complex multilayer protocols.
Such protocols would be far better than what we have now security wise, but it's a huge job. If mjg59 wants that world he would probably be better off doing some social engineering and collecting together a group to write a spec everyone can stomach, not whining about it on the internet.
Sure, people could steal your phone at the same time they steal your laptop. But it's still somewhat less likely, since the phone isn't designed to be plugged in 24/7 in your laptop's USB port, unlike the YubiKey Nano, for example.
That said you can tie both opaque and cryptographic tokens to additional factors. For example, machine tokens can be tied to specific IP addresses, so an adversary won't be able to use them from a different device. Tying them to other possession factors like TOTP codes would also work, though it's often impractical.
Mobile apps can easily make use of signature-based authentication schemes based on keys stored in a secure enclave, both Apple and Android phones have good support for that. For web apps it's more complicated as there's no way to store keys in an enclave (and many older laptops/computers don't even have TPMs), so you keep them lying around in memory or in the browsers' session/local storage, which of course isn't ideal.
[1] https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypt...
In our case, we use JWT tokens that contain a few claims, are signed, have an expiration token, etc. Not awful at all. Verifiable information, signed by us with our private key, exchanged over https. That's not information the bearer of the token needs to be aware of but it is something our APIs can trivially verify and use as a basis for authenticating the bearer of the token. Pretty neat mechanism. Nothing wrong with it. Used at scale by world + dog on the internet without a lot of issues.
And before somebody starts ranting about JWTs being awful: same argument. They don't have to be but they can might if you decline to use sane crypto. So, use it properly and you're fine. It's not that hard.
I use short-lived JWTs that can be refreshed with another token (revokable on the server and gives a nice way to present "here is where you are logged in" to a user in their profile where they can easily deauth a "Login Device"). By using JWTs everywhere (web and mobile) it means all my endpoints can easily verify the token, grab the user id, and perform the allowed actions for that user's given role (I use roles, but you could also use permissions/claims though that can ballon the JWT quickly depending on how you represent the permissions/claims).
As long as you use a good crypto algo, don't set your JWT's expiration for a long time, and reject JWTs that have an expiration longer than your default expiration, you are golden.
What commonly used authentication method doesn't have this exact same issue? Storing the U/P (which is terrible for other reasons) has the same issue. Using sessions has the same issue. Using API keys has the same issue. What auth method are you using that is somehow tied to the device that can't be trivially spoofed?
Tell me again how adoption-at-large of PGP encrypted email is coming along?
If your require these things, do them. If you don't, then don't.
People keep asking to have super smart locks that somehow recognize stolen keys, but how can they?
This is the same problem, just one level further down. The scheme depends on hardware acting against the desires of its owner—whether we're talking about the entire system or just the TPM portion. The privacy concerns are real but not the full extent of the problem.
You want the credentials to be non-extractable. The client wants to extract the credentials and use them somewhere else. Blind trust in an unsupported assertion from a client with diametrically opposing goals is not much of a security model.
Cant have Your cake and eat it too
> client fingerprinting is easily duplicated.
Far from it.
Or we could just use credentials that can be tied to the hardware, and then we have stronger security assertions than tying to IP, and without any degraded user experience.
AFAIK iOS provides hardware keys for fingerprinting
So for example it's not great to use instead of a session key. You could design a system where you have, say, 6 API endpoints and instead of the API endpoints needing access to the authentication db, you use bearer tokens from a single sign on to grant access to the API.
That works great until Bob realizes that Eve has got access to his password and hits a password reset to force Eve out the system.
But with bearer tokens, Eve still has access until those tokens expire.
You could add a check for expiration against the calls, but then we're back to session keys being more straightforward because we're back to having some central information (has Bob's token expired) that needs propagation to the different services.
You could have extremely short expiration but then you're back to having to round-trip to re-authenticate often (or in an auto-refresh situation, one where you're back to checking for expiration yet again).
I think bearer tokens work best for internal proxies / APIs to avoid hitting the data store, they aren't a great replacement for session keys.
I grant that this can happen at scale, e.g. Dropbox, or for sensitive systems that give mutating access to financial instruments, but for the _vast_ majority of businesses, this is a completely hypothetical problem.
GraphQL clients like Apollo can solve this by re-authenticating transparently on error and reissuing the same request.
The issue is: If the user presses the 'log out' button, you want it to take effect within a few seconds. But if logouts are simply a timestamped record expiring, they'd have to expire within seconds, and that would basically mean re-authenticating on every single request.
And at the same time, some webapps have shitty re-authentication flows (like discarding any unsaved work-in-progress and kicking the user back to the homepage) and they're only usable with a re-authentication periods measured in days.
But mostly, the device simply deleting the tokens when the user clicks logout is good enough though. Logout does not protect against mitm attacks of course. But that's not what logout is about. And if you have attacks like that going on, the user clicking logout or not does not matter anyway because you can't trust anything they do since you can no longer tell them apart from whomever is attacking. All a logout signals is the user throwing away their tokens (and probably a lot of other local state).
Which is why world + dog uses either tokens that never expire or tokens with expiration times that are pretty long. In our case we use access tokens and refresh tokens. The access tokens are refreshed regularly and the refresh tokens expire after a few months. So, the only users that we force through repeated login procedures are those that don't regularly use our app. Anything more would be annoying. And yes, if their devices get hacked, those users have would have an issue. That's the tradeoff. The user using a token they just threw away is not a real problem because they wouldn't. The user's tokens getting stolen is a different problem. That's why having the tokens expire eventually is a good thing.
Long expiration times are not appropriate if you are a bank for obvious reasons. So, they tend to use expiration times measured in minutes typically and use additional authentication around sensitive transactions.
The issue with "simply deleting the tokens" is that this offers no way to force another device to log out. You can only log out the device you're using. If you were to lose your laptop, for example, there is no way to go into your account settings and force the laptop to delete its login tokens, hopefully before an adversary can gain access to them—you need some way of invalidating them on the server.
> If you require tokens to be invalidated instantly, there's no way around the notion of doing some kind of lookup when you are validating the tokens.
If you're okay with invalidating all tokens together then you can avoid storing lists of valid or invalid tokens by tying them to some part of the account state such as a token generation counter or timestamp. The tokens would only be valid so long as that state remains unchanged. Logging out would then consist only of updating that one field in the account.
Now I could simply issue a session cookie, or a permament cookie, to the client so we never have to use google again. Or I could make it so that cookie lasts 10 minutes before reauthentication. I can use a refresh token which means they don't need to reauthenticate but if google expires the connection (disabled user etc), I expire mine.
Either way, that's my decision as the person running my site, and it's the clients decision as the person running the client to decide whether to send the blob, or to share that blob with other clients. The only information I know is that somebody authenticated with google at a specific time and I had proof of that, I have no control over what happens to that blob.
Google has nothing to do with what I or the client do with the information.
You're right that I can't audit the third party (google, microsoft, etc), but I don't pretend I can. Google could simply change their password checking to "if (true)", that's google's business. If I'm worried about the third party authenticator then I shouldn't use it, and I should roll my own.
(There was an early Tom Scott video imagining a world where google just says "yes" to passwords[0])
I have an internal read-only blog site using our corporate openid service to ensure that someone accesses the site. Once they authenticate, I allow them access for 30 days before forcing a reauthentication, because it's low risk. OK if they log in from a compromised machine than a hacker gets to read some internal information, its very low risk.
I've also got a site where you can make major changes. You have to reauthenticate far more frequently to make those changes (every 30 minutes). It's a matter of risk.
a) why would you want that burden b) you're never gonna get it
If they aren't, you need to find or implement something that is good enough.
Can you give a more fleshed out example, why should you audit third party services?
That's a legitimate concern, any compromise of the SSI needs a complete refresh of anything it's touched, including refreshing all API keys, keyrings, etc.
Hopefully as the ecosystem matures it'll be easier to en-masse mark an account as compromised and have that status propagate and trigger resets of affected credentials.
A similar threat is insider threat, (probably actually a greater threat) where someone leaving a company could generate tokens which grant them access for long after they've gone.
If reason about how that's typically managed (disabling their upstream accounts) that gives insight into how to deal with your initial concern, it should be managed in the same way.
If you were running the service, then the power to trust those tokens is with you.
Git especially is a federated system that has no need for things like github.
Is this so difficult?
If the client starts using the token immediately, propagating that token to all servers before the first use is a harsh requirement.
A revocation list would be a smaller, easier-to-distribute dataset if you were going to keep data related to specific tokens around.
[edit] I guess that database would get hammered pretty hard on every request, unless you use caching... which might be what you're getting at.
One would think it gets stored in the database, but man all these hashes are so slow to generate. And which library are we using again for that? bcrypt? scrypt? Seems unnecessarily slow. I bet SHA256 is good 'nuff, and way faster.
Lets just meanwhile call the cloud consultant to move this to a separate service.
Have you heard the good word about AWS Cognito? I heard its web scale.
Nah, too expensive, we should just write our own real quick how hard can it be..
I've seen them in http-only cookies which is sent with every request but not inspectable by js. The server can automatically refresh the token every-so-often transparently without the client even being aware or able to influence it in any way.
My preferred way to think about tokens is a form of inter-process communication. In a scenario like 'view this document requires authorisation', the token is a way for the the 'decide whether to authorise' process to communicate some data to the 'view the document' process (that data being 'document-viewing is authorised').
Statements like 'bearer tokens allow hackers to impersonate a user' isn't congruent with what tokens are, or what they're for; in particular, tokens have no concept of "user", "logged in", "authentication", etc. They simply communicate data from one system to another.
The author's complains would be better phrased like "Caching authorisation results is vulnerable to time-of-check-to-time-of-use problems"; that's true regardless of the underlying technology.
Finally, I also consider tokens to be a 'last resort'. From a capability perspective, unauthorised clients should be unable to say what they want; for example, we can restrict access to a document by making its access URL secret and unguessable (we should also allow fresh URLs to be generated, and old ones to be revoked/deleted). That's (a) safer than telling everyone where the document is then having to decide which requests to allow/forbid, and (b) easier to use and integrate into other systems (since it's just a URL; no cookies, "are you a robot?" captchas, etc.).
If we're faced with an action that can't be made secret on its own (like gating it behind an unguessable URL), then we can make it secret by requiring an extra "dummy" parameter; AKA a 'token'.
Note that this is the complete opposite of what the author seems to be describing, which is:
- Reaching for tokens first (rather than as a last resort)
- Having them represent "data" which isn't related to the actions being performed (e.g. "this is person X", rather than "document Y is readable")
- Using this one token to access everything, rather than using fine-grained action-specific authorisation
A server verifying that a client device is "trustworthy" sounds horribly DRM-esque.
That said, I'm a big proponent of zero trust.
My real question from this, though, is who actually does zero trust right? I'd really like to know. I would love to work there and prove it works. Ping me if you do that, please.
OP doesn't propose any in their opinion better alternatives. I'd argue for (roughly, on the move) client-side keys and cryptographic signing (mutual certs basically) and issuing session certs which could be counter-signed, with a short lifetime, but it sounds like they'd be against that too.
Under ZT, how precisely is one to confidently verify local state otherwise? There's a lot of value in the ZT paper and movement, but this particular aspect is both spooky and confounding.
Revokable Private key authorizarion is the best way to go imho. I don't mean at the transport layer but at the HTTP/app layer. This should replace not just bearer tokens but authentication cookies as well. Each connection is authenticated by the final server.
Hopefully you can store the private key on fido or tpm as well to make theft harder, forcing attackers to maintain persitent compromise of your host.
There are scenarios where it still makes sense.
It's plausible that it would more efficient for some high traffic services to push (the somewhat rare) revocation notices out to endpoints as still being more efficient.
For example imagine a whatsapp clone. People send thousands (probably tens of thousands) more messages than they issue password/auth resets.
Having the incoming message endpoint maintain a cache of recently expired tokens would let you set a reasonably long (e.g. 24 hr) token expiry. You could have the app re-auth in the background on start-up and/or when it detects the expiration window is closing, while incoming messages avoid hitting the central DB for auth.
Maintaining a cache of expired tokens pushed to endpoints would be vast orders of magnitude smaller than maintaining a cache of all session keys.
I don't know for sure though, perhaps central session caches are just more practical and secure anyway for these kind of end-user endpoints.
In some ways this is similar to the Certificate Revocation List handles certs. There are certificate revocation notices that are used instead of needing to check centrally the validity of all certificates.
That said, the CRL has essentially been deprecated in preference of a lookup model.
What is larger, the set of all passwords, or the set of all revoked users?
In most cases the set of all revoked users would be multiple orders of magnitude smaller than the set of of all passwords.
Thanks for writing this message, it made it click for me for some reason.
The difference is post-login, where authentication can return for example a 'session key' or can return a JWT or other token that can then be used to say, "I've logged in, I'm xnorswap and I'm allowed in the system until 202204051200Z. I'm allowed access to view users and create reports".
Ideally hardware should have protected private key with manufacturer signature. And API to sign something. Then you can bind session to hardware. But that’s not available today.
Or if not, how would you envision it?
Ah, you’ve discovered the zero trust trap. Don’t worry, advocates will be happy to sell you an ultra expensive security option that breaks more often than it works.
At the end of the day it's stored the same way on the client side. For any large service you need to have a database call anyway.
Why is there so much written about effectively the same strategy?
yeah, I always wondered why there isn't a standard field in the JWT containing a fingerprint/hash of the client's machine/browser/etc.
Unless you use something like a "trusted" hardware module the client has no access too (which is mentioned in the article), but the article is still concerned with compromised machines.
I'm obviously not understanding how bearer tokens work (another user downthread also contends that fingerprint will work, so I'm pretty certain at this point that I have the wrong mental model).
My understanding is as follows:
1. Client logs into gmail/google/facebook/wherever, and gets a signed token-generating-token.
2. Client goes to legit SiteA. SiteA has no access to the token-generating-token granted in #1 above.
3. SiteA pops up "continue with google/fb/etc" and user clicks "continue", at which point google/fb/etc gets a request for a new token using the TGT to authenticate the user.
4. The final-token is returned to the client, and can be sent to SiteA by the client as proof of identity.
5. SiteA can verify final-token with google/fb/etc.
My question is, why can't the token-generating-token (TGT) embed the hashed fingerprint[1], which is generated by the issuing server, and will never be seen by SiteA?
The final-token can use that hash as added entropy into the salt used for the key that signs final-token, because the issuing server will have the fingerprint-hash that allows it to check the signature of any final-token relayed to it.
[1] Browser-fingerprinting may not be totally unique, but it should be good enough (so, you don't include the IP, but the issuing server can figure out what country it is and use that instead.)
The threat is someone with momentary access to the client machine (either in person or via another exploit) can clone the data.
The server has many things to use for a browser hash, including some stuff that doesn't change very often (you would have to exclude things like screen resolution if you didn't want to reauthenticate your laptop everytime you unplug from a dock for example), but they are all based on information sent by the browser, and thus can be cloned by a process running on the client's machine (even including any Elastic Curve magic ciphers which would be pullable from memory)
The only things that couldn't be cloned would be things not sent by the machine, but instead generated by the network -- IP address etc (network spoofing being a completely different attack, although quite easy to do from a coffee shop)
However using that data would be awful as a workflow as many people will change IP address on a frequent basis
The problem is that with many services, the compromise of a client machine can get a token that never expires. The solution is for the service to require reauthentication periodically.
the client never sees the final token, unless the SiteA returns the token to the client, or the exchange of the code happens on the client side. in both cases these are the definitions of incorrect implementation, as the exchange should take place in the backend.
in most implementation you can/should send additional parameters like `nonce` or `state`, which can/should be used to protect against reply/forgery attacks.
Another thing, it's mentioned that we don't control how tokens are created by some third-party, but still, we can use something like Keycloak with external identity provider and client in this case would use token from our Keycloak. In that case we can ensure that for our purposes token will expire quickly.
Overall, hardware-backed identity is probably viable in enterprise scenarios where you don't have the same expectation that company-owned hardware will respect your privacy, and we don't have a great solution for doing it at consumer level, and that probably impairs our ability to offer any sort of browser-level API for it.
Good that you brought it up. That's why I still bake my own verification session system.
Why? It doesn't give them any more info than they already have (the logged-in user!)
If those other sessions are anonymous, why do they have the token?
See, I'm not very experienced in web-dev, and I'm very much aware that I may not know what I am talking about.
I'm just trying to understand how the tracking data in the bearer token will "leak". Can you give me a scenario, like "Client goes to SiteA, is then redirected to login on SiteB which grants the token, and then goes to SiteC which reads the token".
For all you need to identify but you don not want them to be linkable to each other or your government-issued ID.
This is part of why relying on phone number validation for gatekeeping is an issue. (Try registering at Discord or Twitter via tor. I'll wait)
The anti-hype against JWTs is unjustified.
I have a setup of a 24 hour token that holds the necessary information to authenticate the user's requests (like a PHP session really), along with a 30 day refresh token.
My Vue FE detects when a 401 status code is returned (using axios) and attempts to refresh the token. If this fails, the user is redirected to the login page.
It works quite well IMO. This setup lets me authenticate cross domain requests without any trouble. I was previously used to doing everything same domain via standard PHP sessions.
The progression then becomes to reduce it from 24 hours to say 5 minutes (to reduce the attack window, which may or may not be adequate). It then becomes almost the same as validating the user session on each request (not quite, but you start debating if using jwt is worth it at all).