Hacker News new | past | comments | ask | show | jobs | submit login
Hardcoded secrets, unverified tokens, and other common JWT mistakes (r2c.dev)
188 points by todsacerdoti on June 27, 2020 | hide | past | favorite | 82 comments



Well the fact that you need to 'choose' a cryptographic protocol out of the suite of ciphers reminds me of why the creator of WireGuard opted for no cryptographic agility and instead chose versioned protocols, unlike OpenVPN.

From the Wireguard paper [0]:

> 'Finally, WireGuard is cryptographically opinionated. It intentionally lacks cipher and protocol agility. If holes are found in the underlying primitives, all endpoints will be required to update. As shown by the continuing torrent of SSL/TLS vulnerabilities, cipher agility increases complexity monumentally.'

[0] https://www.wireguard.com/papers/wireguard.pdf


If someone wanted to utilize JWT in a new system, and therefore could freely choose any of the JWT options, what would the "most secure" be?

It would probably save a lot people headaches if jwt.io published a chart of "use case" and "algo/cipher selection". From what I've researched, all the JWT code libraries give you a menu of selections, and it's on you to research which algo/cipher to use, and given the volume of selections, that's a substantial reading list.

Is there a 'versioned' JWT that picks sane defaults as they are developed + improved?


Here's your recommendations: https://security.stackexchange.com/a/233863/111020

And yes I just spent 2 hours writing that.

There can't be a great authentication/crypto that can improve over time. In theory, it must block old things to be state of the art. In practice, it's used in client-server communications, that would break all communications if nothing could talk unless they're always on the exact same version.


Good recommendation. I should add that RSA is faster on verification but slower on signing than ECDSA. For most application, the amount of traffic is small enough that the difference doesn't matter but for large enough traffic, you should consider the difference. The performance of course depends on a lot of factors but

https://connect2id.com/blog/nimbus-jose-jwt-6

would give some reasonable idea on the practical difference on modern hardware.


Lovely. Thanks for writing this.


Paseto appears to be something that attempts to be "JWT but with sensible choices already made".

When it comes down to it, you need some crypto knowledge to make these choices. We constrain them to PS256 and ES256 in our system (but then you also need to make sure that the curve used for your keys for the ES256 signature is an acceptable one)


Yeah, at this point, just use PASETO and be done with it. It's not worth trying to figure out which JWT misconfiguration will burn you next.


From pypaseto

“ This is still in early development. It has not been reviewed in a security audit yet, so please be aware that it is not expected to be ready for use in production systems.”


Is that remark about that specific implementation or the spec as a whole?


Just for the specific implementation. The reference implementation is the PHP version here:

https://github.com/paragonie/paseto

And has had a lot more attention.


The SPIFFE JWT-SVID standard attempts this, although for a specific context: https://github.com/spiffe/spiffe/blob/master/standards/JWT-S...


These failure modes have been used to criticise JWT over the years. But all of these errors are of the same level as "don't concatenate input strings into SQL strings" or "don't store passwords in plain text". That these errors are made says much, but not about the technology, IMHO.


At least those are well known, no common web framework will save password in clear text, and there is comprehensive literature on how to use SQL and many well written ORMs that will make it very hard to shoot yourself in the foot.

For JWT, it's essentially promises of easy authentication process (rarely talking about the hard things like logout) spread like landmines over Medium and blogs, waiting to blow your foot and all your application with it.


Regarding logout, I think that's a fundamental issue with a stateless solution like JWTs. I mean, the whole point is that it is a self encapsulated token, so you don't have to go back to a central server to check if someone's login session is still valid. So how on earth do you invalidate it?

My boss wrote up this article on different ways to revoke JWTs: https://fusionauth.io/learn/expert-advice/tokens/revoking-jw... which examines a few interesting methods.


For many applications "slow" logouts are enough, i.e. basically do what is common in OAuth2 setups:

- Have a refresh token which is only usable with the (right) auth server and can only be used to generate new access tokens. As it's only usable with the auth server it's easy to revoke (it's kinda like a session cookie).

- Keep validity of access tokens short.

So by revoking the refresh token the logout will be done in at most highest_refresh_token_start_time + refresh_token_validity.

Through sadly any "faster" logout method on a distributed system is indeed quite complex. (Like propagating bloom filter headed blacklists of early revoked access tokens).


> For many applications "slow" logouts are enough

Why? I understand it might be acceptable for the next "Uber for Cats" SaaS but if you protect an account that is actually important, not being able to revoke an access key is an issue.

> Keep validity of access tokens short.

But then you are actually spamming your authentication service and it even starts to blur the distinction between access token and refresh token.

I understand that there is many specific use cases where JWT makes sense like when you have to delegate authentication but for other case which shouldn't be recommending it, not being able to properly logout is not a security practice we should have in 2020 and later.


What better tool do you suggest that can handle logout? It's 2020 and there are none.

OpenID Connect with JWT handles logout just fine if you really want it. Call the /oidc/tokeninfo to check the token is valid instead of verifying the signature offline. Of course this doesn't have the benefits of a decentralized token anymore. Can't have the cake and eat it too.


Doing regular, boring authentication using user/password just supports logout out of box.


So regular cookie session based auth? There are drawbacks such as managing session state on the server, and increased latency and cpu load, since you now have to have a db call on every action.


The argument is that this DB call, or something much like it, is inherently necessary to support logout. It's not something unnecessary that can be optimized away; getting rid of it entails losing functionality.


>rarely talking about the hard things like logout

Why hard?

You can always black list them


But then you have have make a database call to check the blacklist, so you might as well use sessions


I'm not sure about it, but what if Logout just removed token from client meanwhile "Logout from all devices" black lists token?

I tend to believe that it could be used by gmail, but it's just a guess


If you're blacklisting tokens at any point in your application, you then need to check this blacklist.


It goes into the same category as criticizing C's memory safety. The errors do say a lot about the tech being prone of misuse, and that's not really a good thing.


"don't concatenate input strings into SQL strings" is a pretty good comparison, because the rise of ORMs and APIs that make parameterized queries ergonomic has done probably 10 times as much to stop SQLi in practice as telling developers to sanitize their inputs ever did.


>sanitize their inputs ever did.

was even escaping input viable strategy?

if you escape thing, then your data in db is broken

parametrization seems like the only strategy out of those 2


some parts of a query cannot be parameterized, e.g. the column name for an 'order by' in many cases. So you have to do some kind of escaping, sanitation and concatenation.


If you're escaping/sanitizing to massage an input string until you can concatenate it into a SQL query, you're doing it wrong. Please don't.


While this solution isn't sexy, cannot it be just simple switch?

switch(int)

case 1: age

case 2: salary


Yes, unless the database has custom column names e.g. to add your own properties to some object. But that kind of schema is madness in other aspects as well...


I have yet to dream up/see a case where you can't puzzle literals together based on user-query parsing until you get your SQL query assembled.


Agreed. But writing your own authentication frameworks are a virtually identical situation.


An authentication protocol that has this many footguns and insecure defaults is probably just not a very good authentication protocol.


That's similar to saying that CRUD apps are bad because you can have SQL injection (as others have mentioned). Or that username/password is a "not very good authentication protocol" because some folks store passwords in a database in plaintext. Sure, the "none" algorithm is a footgun, but just make sure you don't allow it.

Maybe I'm talking my book (because I work for a company that sells software which generates JWTs) but I think it isn't that hard to securely validate them. It's just matching on JSON keys, mostly, and can be wrapped up in a library (like https://github.com/jwt/ruby-jwt).

I also wrote an article on how to build secure JWTs: https://fusionauth.io/learn/expert-advice/tokens/building-a-...

I guess the question is, where's the balance between the responsibility of the application developer vs the RFC authors? That's an interesting discussion to have, because I bet there is at least one valid use case for every one of the JWT RFC sections. (I've even heard of valid cases for the "none" algo, like if you are very concerned about performance and have other ways of validating your clients, such as mutual TLS certs.)

I've never written an RFC/standard, but I bet it isn't an easy task.


That's the thing, JWT is not an authentication protocol, that's just one (the most frequent) use-case of "transfering claims". The footguns, e.g. the choice of encrypted or not, or symmetric or asymmetric crypto, are a result of the flexbility required to cover the other use-cases. Maybe what's needed is a subset of JWT that's just for AuthN.


Irrelevant. If the application doesn't call the function to verify the user token, the application is insecure, it doesn't matter what protocol or token format is used.


My response to that would be "Parse, don't validate" ( https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va... ).

In other words "the function to verify the user token" should also return the payload data (e.g. the user ID, or whatever), and it should ideally be the only way to get that payload data. That way there's very little that can go wrong:

- If the verification function doesn't get called, then the application doesn't get its payload data (e.g. user ID), and hence can't be misusing that data.

- If the application is using the payload data, then it must have come from the verification function, and hence has been validated.

I'm adding JWTs to a system right now, and forcing this interface by encrypting all of the payloads. The only system with access to the decryption key will refuse to decrypt tokens which aren't signed, valid, non-expired, etc.


You ignore the fact that some protocols are easier to "do wrong" than others. The point other people are making is that JWT is one of those.

You're welcome to disagree but don't gloss over the fundamental point being made.


To play devils advocate say the design by the library was that you could not read the jwt data without the library verifying it.

That goes away.


What would you suggest instead?


Ultimately, everyone has a first time working with some particular technology. If doing the right thing requires knowing specific coding patterns, there'll be a lot of cases where people do the wrong thing.


Of course you can improve the tooling but it's not the only conclusion.

If you don't read the docs, don't have the right mentoring, etc etc, your first go at anything is likely to be of low quality. Calibrate your confidence better, especially for anything security-related. Don't put it into production if you've only skimmed the docs (the mistakes we're talking about are novice mistakes).

Another conclusion you could draw is "use a built in security library not raw JWTs". There's just as much reason not to use a security library as there is not to use a SQL ORM.


But doesn't JWT sometime force people to use it in an insecure way because of how it is designed?

In a JavaScript browser app without backend server it is impossible to use it safe. The token must be visible to JavaScript which is unsafe by default.

You also need to store both the token and refresh token in your app which makes the refresh safety feature useless.

I still don't get JWT. It solves requesting an auth server all the time but when the token is stolen it can be used until it expires. This can be solved by setting the expiration very low but then the auth server is still requested all the time.


The only insecure-by-design authentication scheme is passing the user identifier through HTTP headers (it's unfortunately quite common). Headers are set by the web client, so any client can assume to be anyone.


Thanks, for the article. Always good to refresh the common pitfalls.

There is a RFC which also details the best practices for JWT: - JSON Web Token Best Current Practices: https://tools.ietf.org/html/rfc8725

On the similar topic, some more interesting RFCs / Drafts from IETF on OAuth: - OAuth 2.0 Threat Model and Security Considerations https://tools.ietf.org/html/rfc6819

- OAuth 2.0 for Browser-Based Apps - https://tools.ietf.org/html/draft-ietf-oauth-browser-based-a...


Reminder: If your jwt only contains a session_id from your framework, which you then look up in a database every request, you absolutely should not be using jwts.


I'm using it for cross server auth


Regarding leaving the secret in your codebase during development... always make sure the secret is not in your git history. Change it when you move it out of the repo. I know this seems obvious, but I always do a git history on files that might have tokens or secrets in history and you'd be surprised how often I discover them.


> you'd be surprised how often I discover them

Oh yes, now defunct but we had a search engine just for that :| http://archive.is/0GNLl


I evaluated JWT recently and found it so overcomplex and full of footguns that it was unacceptable for a security-critical component. It boggles the mind that this was designed as recently as 2015.

I plan on going for a simple bespoke solution which just uses an off-the-shelf algorithm to sign a structured payload (likely BARE), which in addition to being less full of shit will make for smaller and more managable tokens.


Can you elaborate on this?

> ... full of footguns that it was unacceptable for a security-critical component.

What it not secure about it?


See TFA for a subset of the problems. Security should be designed so that it's easy to do it right and hard to do it wrong, but JWT offers many opportunities to do it wrong. Maybe you can figure it out with enough reading of TFA and other articles online, but there's no reason to take a risk on such a shoddy security standard in the first place.


Interesting article. On this note, recently as part of small group of people we needed to implement API -> API authentication. Unfortunately, we don't have anyone who has solely implemented this before, so we took to searching to try and pick something resembling industry standards. Of course Oauth 2 got brought up, however the spec and articles on it seem bizarrely out of sync with out needs. Notably on two points:

1. It seems any piece of content talking about Oauth 2 is referring to 3rd party, external authentication. Ie i want a client API to talk to my API but with an identity managed by a third party entirely. This is not our use case, but seems to be the primary focus for Oauth 2 articles.

2. The rest of the spec _feels_ very loose for our use case. There's loose definitions _(it feels like)_ on some basic patterns of tokens and renewal tokens, but it all seems so wildly flexible that we're almost lost on what to actually write, what technologies to use to generate tokens, etc.

Any tips for narrowing the field, to implement the right thing, in the right way? This article struck a chord with me.


most of my knowledge about oauth is about using it at my dayjob for years, so don't take my answer with any authority. its more a users perspective of the technology then how it was actually meant to be used, which i do not know.

first off: the whole idea of using oauth in your systems is to externalize authentication. its main usecase is if you have several services which share the same users... usually microservices, but everything else which supports oauth works as well, obviously

if you do not actually want to externalize your authentication, then oauth is likely not what you actually want. (keep in mind this only applies to the process of authentication. you will probably still create a `users` table to put a foreign key on your domain entities. said users table just won't have a `password` field or similar)

under this perspective it makes sense that you basically only find blog posts to integrate 3rd party tools, right? you're just setting out to implement a new oauth2 server otherwise after all, and... i'd strongly suggest you don't. there are so many pitfalls you can fall into. its not a trivial subject.

thankfully, there are readily available FOSS oauth2 servers like keycloak around if thats what you want to use...

but to come back to my initial point: if you do not want to externalize your authentication, then do not use oauth! almost all frameworks already have tools available you can use to authenticate with api tokens. it keeps you within the bounds of your frameworks strength and you can automatically fetch a token on your frontend after the user authenticated with your usual flow.

but i'm already off topic considering your first question. if you do use a solution like keycloak, there are `oauth clients`. these behave exactly as you want.


> first off: the whole idea of using oauth in your systems is to externalize authentication. its main usecase is if you have several services which share the same users... usually microservices, but everything else which supports oauth works as well, obviously

We don't want to externalize it. For clarity _(because i think i implied internal API->API)_, our primary concern right now is a client API interfacing with our API. We want to choose some implementation that clients would expect, and easily reason about.

There may be a future where those clients have to authenticate to multiple APIs / microservices of ours, but currently it is one API setup for this explicit purpose.

> but to come back to my initial point: if you do not want to externalize your authentication, then do not use oauth! almost all frameworks already have tools available you can use to authenticate with api tokens.

Yea, not using Oauth was my thought was well. My concern however, was trying to pick something clients would expect. I don't want it to feel custom, arbitrary, hodgepodge.

Our very early impression / plan is to use a token renewal, and a shortlived token, similar to oauth. However this loose, and custom, and i don't want clients feeling like we're making things up. Hell, i don't want to make things up.

My current thought is that I need to research JWT more, as it may fit what we need, and be more standardized.

edit: And PASETO


You should probably just try out a foss oauth server to get a feel for it.

Just start a keycloak server with docker and write a small Webservice in your language of choice which only let authenticated users open the website, which just prints the user name for example.

After you did that, you could write a second service which accesses that api using a client (not user) and prints which users have accessed it since it was started.

That should be doable within a few hours at most and give you a feel for the technology.

If you're fluent in python, I'd personally suggest just starting a hello world flask project, connect it to a keycloak and write a second cli script which simulates the non-user access. (If you use Java, keep in mind that springboot2 uses Springsecurity5, which was incompatible with the official keycloak Java sdk the last time I checked. Either use springboot1 or expect to have a slightly harder time figuring out what to write in the application.properties)

Reimplementing advanced authentication systems like jwt on your own is extremely error prone and frankly unnecessary if you're not in the business of authenticating users. I'd suggest to either use whatever your framework prefers (which is usually just a static and very long, manually rotated token) or try to externalize it by using readily available foss solutions


> Yea, not using Oauth was my thought was well. My concern however, was trying to pick something clients would expect. I don't want it to feel custom, arbitrary, hodgepodge.

I think clients often expect one of the following (assuming everything is done over HTTPS): 1. HTTP basic auth 2. Login to the API, get a token, and use that token for requests 3. Generate an API key for the client and the client sends the key on every request 4. (Occasionally) Cryptographic signing of every request, sending the signature with the request

1 and 3 (if implemented as a username/password on the back end) are going to have similar trade offs. You are fully authenticating every request that comes in against your credentials data store.

Option 2 is where OIDC/OAuth2 comes into play or you could use a standard session token approach if you want. In OAuth terminology the client credentials grant is the simple flow where the client logs in with a username/password, gets a token back, and includes that token in API calls. Option 2 is also valuable if you want to support mobile clients with refresh tokens and such using things like the authorization code grant. Essentially allowing users to login to a mobile app once but use short-lived tokens when calling the API without prompting users for credentials every few minutes.

Option 4 can be an alternative to token-based approaches to reduce load on your credentials database but gets more complicated for the client.

Ultimately if you are looking to keep things simple I would lean towards HTTP Basic auth until/unless you are building a mobile app or otherwise really need to move to a token-oriented approach. At that point I would consider full fledged OIDC/OAuth2 or maybe a login that generates an API session token.


I've been doing a similar thing recently. I found PASETO to be interesting ( https://paseto.io ), which is a lot like JWT but is simpler and more opinionated (e.g. there is no plaintext option; symmetric and asymmetric operations are cleanly separated; etc.). I especially enjoyed watching this video https://youtu.be/RijGNytjbOI

I still decided to go with JWT, since PASETO isn't yet standard and its software hasn't been around for long, but it definitely helped me understand more about the choice I was making. In particular, PASETO distinguishes between "local" tokens (producer and consumer can both use the same symmetric key) versus "public" tokens (producer and consumer can't use the same key, e.g. third party services run by different organisations; hence asymmetric keys are needed). PASETO encourages local tokens to be used when possible, since their keys can be shorter, they generate shorter tokens, they're faster to encrypt/decrypt, they're easier to rotate, etc.; public tokens are essentially a last resort when we must interact with a third party.

I was originally considering 'public' JWTs (i.e. asymmetric keys) for my use case, since we're using AWS API Gateway and that has the option to check the signatures of 'public' JWTs, and reject invalid ones before they ever hit my code. However, I eventually went with 'local' JWTs (i.e. symmetric keys), since the convenience of having AWS discard some tokens for us wasn't worth the (mostly logistical) overhead of using asymmetric keys.

My choice to use the JJWT library was also partly due to its recommendation by the JPaseto library ( https://github.com/paseto-toolkit/jpaseto ).


PASETO looks interesting, thank you!

Since my primary concern is external client APIs communicating with ours securely, but also in a way that's not too-foreign. I imagine JWT would fit the bill of being widely known, but PASETO sounds really interesting too - having less things to get wrong sounds amazing. My concern with PASETO is that client APIs are huge slow customers, so i'm hesitant to choose anything that's not super mainstream.

I'll research PASETO more, but i feel like my decision would come down to fitting to what the customer can use - less change is more, in that case. Ie, JWT.


Jwt works in this case... You issue a key to a known user.. In this case you create an API user (doesn't have to exist in a backing store) and set the expiration to a super short duration and validate it on the other end... You can also use this for API testing in unit/integration tests


OAuth is perfectly valid as a standard for inter-API authentication but it is complicated. You'll also have to figure out separately how your oAuth clients manage the secrets they use to authenticate to the oAuth server to retrieve access tokens, and if encryption as well as authentication between the APIs is important to you, how to manage the keys to do so.

Take a look at https://spiffe.io/ which avoids these concerns focuses specifically for system to system authentication at scale.


This is extremely context dependent. With just a few services, you can generate and statically place a few random strings to use as bearer tokens. In a more complex microservices environment, you want some way for the scheduler to participate in identity issuance, for example SPIFFE. Also consider whether the applications can actively participate in the scheme, or if it needs to be abstracted through sidecar proxies. Also consider whether you are doing TLS. Bearer tokens on plaintext connections are weak; maybe you need HMACs. Or to sort out TLS first. Then consider whether you need tokens at all, or can just use client certificates. Etc.


Where are you blocked in this use case?

The easiest flow IMO: The API client needs a service account (username and password like a human). It authenticates to the OIDC server with the username/password and obtains a token. Then it can call any API service with the token.

It's quite straightforward really.


Simply not hard coding your secrets is a good first step. Even better is to think about how you’re going to rotate secrets. So much easier if you build it in from the start than if you need to tack it on later. Once you have secret rotation supported, you can set up mechanisms to do it regularly and you can rest a bit easier at night.


This is why looking into JWKS might be a decent first step.


We'd recently moved our jwt code into rust (using the jsonwebtoken crate), and it's wonderful to have this article to read in that context. The pitfalls listed in the article were mostly handled by design, so we had to correct mistakes in the PHP implementation that were impossible to make now.

Eg we'd been using the `iss` field, a claim, to determine which key to use, when we should've used the `kid` in the header. All claims are now completely inaccessible until verified to the point where there's no way to access them short of manually deserializing the jwt. We'd also allowed some invalid tokens where the `exp` was in the past, only if you were refreshing the token of an active session. This could've caused problems if we ever wanted to issue a token that expired very quickly, since you could have a token that's theoretically 10-times longer lived than you intended.


I only see a list if four "Don'ts". Would have been nice to have some "Do instead's". Of course I can try to think about "good" ways to access my secret other way than hard coded, but then I may not be the audience. If I already made the mistake of storing keys hard coded, then how should I know that my new way is better than this?! For articles like "X mistakes in topic Y" I expect some solutions to solve the problems. The other way is only pointing at fools for me.


This otherwise good list omits the most pervasive mistake, which is not to use stateless JWTs for authentication.


For (almost?) all security components the worst and most prevalent mistake is going to be "That was not the correct component to solve your problem".

Here's a nice real world example:

* Expensive combination padlock on a cheap shared property. The building owners decided our bin shed should be locked, they bought a nice padlock, installed the lock and mailed out the code, residents don't need the hassle so somebody doesn't close the padlock, thieves who would never want to steal our garbage do want a free padlock... no more padlock.

You give unnecessary JWTs as an example, but here's another way too many people choose the wrong component:

* Public key cryptography is pointless unless your private keys are private. If your system involves centrally minting key pairs and then sending them out to people you don't actually have a public key cryptography system, you've got a secret key cryptography system in which you are (at best) wasting a lot of resources and getting no benefit. Way too many corporate VPNs are like this. Most S/MIME and almost every private SSL CA I've seen is the same.


Could you elaborate on the why here?


From my understanding, the use case for JWTs is where:

1) One system produces a JWT which makes a bunch of "claims" about the bearer; e.g. 'their email address is alice@example.org'.

2) Another system which trusts the first system can consume these tokens and use the "claims" to authenticate the bearer, e.g. 'the email address alice@example.org is associated with user ID 12345, so the bearer must be user 12345'.

3) Now that the user is authenticated, the second system can initiate a session for them (e.g. storing a cookie in their browser, or whatever).

4) From now on that JWT should be rejected; we can approximate this by making it short-lived, but we can do better by the second system update some state. For example, updating the "last seen" time of a user, and rejecting any tokens issued before the this time.

The idea of 'using stateless JWTs for authentication' is presumably the common practice of avoiding steps 3 and 4: rather than having the second system initiate a session for the user, the JWT is treated as if it were a session token; rather than invalidating the JWT after its first use, it is accepted over and over again as part of the user's subsequent requests. This also requires the JWTs to have a longer expiry time than necessary, since they have to survive until the user's last interaction, rather than their first (in fact 'refresh tokens' can be used as well, but the principle is the same).

As for why it's a bad idea, I recently came across PASETO (a simplified alternative to JWT) which specifically calls this out as an insecure practice due to the possibility of replay attacks ( e.g. https://github.com/paragonie/paseto/tree/master/docs#was-sta... )


JWT (specifically JWS) is essentially a simplified modern alternative to X.509 certificates and I think it should be perceived in that way.

Do we use certificates for authentication? Yes, and it works well.

Do we use certificates for authorization? Not likely. Instead, usually we ensure ID of entity which we communicate with (as result of authentication) and lookup it's permissions in database.

Do we recognize any certificate body as immediate permission requisite? No! Instead, we have signed claim with public key which entity should present in order to prove it's identity (and prove possession of private key later). I. e. we issue end-entity certificate which describes and verifies public key of that entity.

Do we NEED to revoke these end-entity certificates or have them expiring really fast for normal operations like logout? No. There may be something similar to CRL and OCSP for handling emergencies, but this is optional. Instead, we are not trying to handle authorization tasks with authentication mechanism. It's different things. We may just change status for authenticated entity in authorization database. Like: "user:admin;device:XXXXX;status:terminated".

What semantical meaning has revocation/expiration of token issued to alice@example.org? She is no longer alice? I doubt that. She is no longer allowed here? Or her specific device is no longer allowed here? Or her service subscription is just expired? Either way it's not a question for part which recognizes known entities and must work in a "yes" or "no" fashion.

It's not wrong to use JWT for authentication. It's wrong to use ANY authentication for ANY authorization.


Using something stateless when you want state always leads to problems. It is very convenient, for example, when you give the end-user a token that cryptographically proves that they're a certain user. They provide that to the server, and it doesn't have to access any state to allow access, saving precious milliseconds of latency. The problem comes when someone else steals the token or the user's authorization is revoked. Since you aren't checking any state, you have no way to say "oh we fired them, kill their access" or "their token got leaked, shut it off". This leads to problems where malicious access can persist and you can do nothing about it.

I feel like the problem is generally ignored to some extent. I was writing my own (stateful, one database lookup per request) SSO system and ran into other problems. For example, how do you terminate all TCP connections that the user has open when revoking the token? No proxy I could find had that level of granularity exposed through its control plane API. They are all living in an HTTP/1.1 world where requests were short-lived, and it was acceptable to handle revocation at the next request. I kind of doubt it's ever caused any problems (employees on their last day don't seem to open long-lived gRPC connections to internal infrastructure for whatever reason), but it's one of those things that's going to burn someone someday.

(I seriously considered modifying envoy to keep track of (user, downstream TCP connection) so that they could be forcibly closed via an API when necessary, but decided a global rolling restart of the frontend proxy was acceptable in the extremely rare instance when some sort incident required terminating all of a user's TCP connections. Engineering time vs. security tradeoff. But then there are companies with 100s of developers that will ship you custom software to manage authentication, and they don't have this feature. That seems shady to me. Or maybe not so much shady, just exceedingly lazy, like they haven't really thought much about their product.)


Weak secrets is also a common issue. HS256 and similar algorithms allow for offline bruteforce attack.


I would guess that secrets are also often reused over different environments like test and prod.


Anything when not used as it was designed to be used is dangerous.

JWT is a very useful technology, one that increases UX by decreasing overall latency.

Any technology, can be "abused" or used by people that do not understand how it works, and as a result you will have an insecure system.


The technology here is digital signatures.

JWT is a standard for using that technology, and one that makes several design decisions which make it fragile.

This is the difference between, say, the idea of a car (and the benefits thereof) and a Ford Pinto.


I remember JWT coming on my radar around 10 years ago during the height of RESTful bikeshedding. It was a "must have" because sessions, being stateful, are not pure REST. Now that we've come off the height of the pure REST fanaticism and have learned about a bunch of issues that JWT causes, is it really better than just having a session id and a session stored on the server? Does it actually provide any technical benefit? Honest question.


These are orthogonal concepts. A session ID is just a token that the server uses to lookup some state about the request that presented the token. A JWT is a token that can be used to present a claim of who the requestor is (and the server can verify it). A session ID token doesn’t help my request prove I am who I say I am when I call your API for the first time, unless you’ve implemented some sort of state store that all of your API services and server share.


> unless you’ve implemented some sort of state store that all of your API services and server share.

Yes, it's called session storage, and it used to be incredibly common. These issues are not "orthogonal", because a primary promise of JWTs were the ability to get rid of that shared session storage and just put that identifying info into the signed token.


JWT's issues are really quite simple to avoid. Spend a day reading how it works and the pitfalls and you are good. And they offer many advantages over sessions with the only real disadvantage being revocation.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: