-
-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JWT encryption #67
Comments
Hi @christophweegen , I am reluctant to add encryption to the token, because it means adding complexity and more dependencies for something that, in my view, should not be necessary. In my view, no business secret should be added to the payload. I would not tell that the id of a record is a business secret, because a lot of times it is used to build resource URLs. And for other information, like the role of a user, I think there are two good reasons not to add it: security and the possibility that the information becomes no longer valid before the token expires. Anyway, by now, I will leave the issue open to give it a second thought and to allow further discussion. By the way, the issue should be addressed in warden-jwt_auth, because in fact it has nothing to do with devise.
I would need more info to address it. I know no incompatibilities of devise-jwt with other gems. Maybe is it a rbnacl-libsodium issue? |
Hey @waiting-for-dev, About business secrets in payload:To make it more concrete what I had in mind, consider this use case: You maybe have an app with a good user base and you want to hide how much users you actually have, so your competitors don't know about the count of your users. If you take the standard rails way of having consecutive user_id integers you could simply open up a new account, sign in and read out the freshly generated JWT and you get an up-to-date user count ( at the moment of your registration ), maybe substracted by an estimated amount of unused ids from already unregistered users. Having a roughly estimate of the count of your user base is a valuable insight as well for competitors as investors. You could even write a simple script that logs the daily growth of your user base this way. The same could be said about every other resource of your site where maybe plain integer ids get included in urls like /posts/123. YouTube is avoiding this for example in their video urls with a hashed query string. For example https://www.youtube.com/watch?v=m1jOWu7woKM&t= ( part 2 of a nice new video series by DHH btw. ) That's why gems like hashid-rails actually exist, so you don't have to include plain text ids in your page. I wanted to include that gem in an --api rails app but wasn't quite sure how to combine it with devise-jwt, but thought there could be a better option for example encrypting or hashing it directly in But the hint to A short glimpse at the Other examples of important use cases for an encrypted JWTOne of the main aspects of the JWT philosophy is providing a way for tamper-proof, stateless client-side "sessions". The ability to store arbitrary data on the client side, without having to maintain state on the server is actually the main reason why they we're invented. If we can't make full use of all the goodness that JWT provides it would be quite a waste. If we wouldn't want to bring them to their intended use of storing data other than a user_id, why shouldn't we even implement them at all? The only difference between a JWT and a random, unique and signed token, which identifies a user as well, would be one additional row in our database broadly spoken. The use cases to put only 100% non-critical data in it, would be quite limited too, since almost any ( especially user ) data that lies on a server that needs authentication is confidential or can be abused, at least in most applications that aren't intended for publishing, like online-banking, online-dating, accounting software and so on... There even wouldn't be a JWT spec and if they weren't intended to hold confidential data no JWE ( JSON Web Encryption ) spec either. Look at this link too, which defines a whole lot of possible JWT standard claims, whereby many can be undoubtly considered as not secure for using them in plaintext JWT. Usually signing the JWT is enough, to make them secure in terms of validity, since you can't tamper with them. But security of course is also depending on their content, even if the JWT is totally valid. A clueless user could put all of these claims in a JWT while using There is one thing too that always bugged me in almost every JWT tutorial i came across. Everywhere the concept of "you can put whatever information you want into them if you like" was mentioned and how "secure" they are, since nobody can tamper with them, so you can always trust on the data provided. But no one actually mentioned explicitly enough, that you should take great care what you actually put in. If the content is somewhat confidential in one or the other way, even if you weren't really aware of the confidentiality and the implications that could arise from exposing that data at that point in time, it should be a good idea to encrypt it. You still have client-side stateless data but with an actual layer of security on it. But rarely they ever mentioned that a JWT can pose as a rather negligible to a quite severe security threat. Just consider the following lines: User_ids, as described above, are one of the examples that i consider as confidential data, even if they're just integers, but if you think about it longer they can actually include confidential information, like the growth of your user base. Consecutive ids can also show an attacker in which id range he has to dig to match actual data. Another example if your site is prone to SQL injection, an actual user_id could be quite handy to extract data specific to that user, without having to make big, many or unusual database accesses which could be a warning sign for security measurements. Just some thoughts on that, maybe there are other possible vulnerabilities. Since I'm not into hacking other peoples websites my malicious imagination is rather limited :-D But if you think I'm just picky, pedantic or even paranoid over encryption and security ( actually I guess I am :-D ), consider the following quite possible and devastating scenario, which came to my mind: First ThoughtsA JWT with user_id is used for authentication and additionally containing the contents of an online shopping cart to keep track which items a user put in. So on each Maybe the web developer of that shopping site means it way to good with the front end developers or overdoes it with the JWT philosophy and even includes the name, email, phone number and address of the user in the JWT additionally to storing it in the database, so you're frontend framework always has the right user name for easy displaying and the email, phone number and address to fill your user profile page without making a DB roundtrip each time these data is needed to be displayed at the page, which could be quite often. The more is in the JWT, the less work has to be lifted in each request by the database, right? Saves load on the server and thus money. Can also help in reducing the size and complexity of the database. So actually maybe a good solution to consider, for data which isn't needed to be 100% permanent. For example it wouldn't actually be the end of the world if you maybe lose the three items of your shopping cart, because the JWT gets deleted or your device doesn't boot anymore and you have to reset it. And any other important and permanent information you may lose is stored in your database anyway. No problem of putting confidential information in the JWT too, because the same-origin-policy makes sure no one has access to it, if you put it in localStorage, right? And if there even isn't a part in the documentation of a gem or in tutorials which talk thoroughly about that it's sometimes very important to encrypt your JWT, how bad can this issue actually be? When i first read about what a JWT is and realized that they aren't encrypted by default, I was quite a bit confused, because in my imagination the use cases for encrypted tokens are much more plenty then for plaintext and could be rather dangerous if treated careless since they are plaintext by default( i know base64, but in terms of security it is actually the same as plaintext ) and should be actually encrypted by default, to provide at least a little obstacle on your way to maybe shoot yourself in the foot. Someone knowing your name, email, phone number, address and some random numbers isn't such a big deal either, one may think. Amazon, Ebay, Google and many other, maybe much less trustworthy websites may already know all that stuff about you since years and you will maybe never even know if there was a leakage of your data at their place already too, so who cares for that little information you provide at all? Additionally for example business partners and every other individual can see your whole name, email, phone and address of your (home-) office in the contact section of your business website already since quite a while, so there's nothing which is a real secret anymore about your user data, one may think too. So why should a user/developer actually care for this to keep this data private by default in a JWT? Plaintext is quite handy too, since it even has the benefit of using that data in your frontend scripts? Doesn't really make sense to encrypt JWTs at all in most cases if you think about it like this. The answer is: The real problems lies in the association with other data, which also looks innocent on it's own, like arbitrary integer ids of arbitrary resources. To make it quite vivid: The NaughtyStore™ ExampleNow consider you're shopping at If there's now a XSS vulnerability in the site, maybe in a product review section with unescaped customer comments, an attacker could easily steal your JWT and have all the data of your shopping cart exposed. Or if the site allows an external, even "naughtier" advertiser, to show ads on the page and carelessly puts an malicious advertisement script on the page, provided by the advertiser, to show the ads, while it clandestinely extracts your JWT and maybe send it to Now imagine the site has maybe 10,000 customers. That would be 10,000 people prone to blackmailing about their maybe "unconventional leisure time activities". Even if the item-numbers/ids are stored in "innocent looking" integers or random strings, it's usually quite easy to reverse-search the associated product. Most sites allow to search their products by item-number. I would say by average you would only need the names of 3-4 products a user put in their cart, to convince someone by blackmail, that you even have "even more embarrassing" ( as in quantity and quality ) information about her/him, even if you don't have that information at all and only those 3-4 product numbers. I guess they will directly pay, if you say you would reveal it at family, friends and work, if they don't pay or do whatever you expect from them. Now you can say: "Even if the JWT is encrypted, an attacker which gets hold of my token through XSS or another vulnerability could put that token in a crafted But when you actually recognize the XSS vulnerability as a website owner you could just change your JWT signing key and all encrypted JWTs, stolen or not, would be worthless to the attacker from that point in time if he hasn't already extracted that confidential data by signing in as the victim. A good idea is to store an "exp" claim too which expires that token after a while ( I've seen Now consider this: If the token isn't encrypted, all of the information in the token is accessible to the end of time since the whole token is available in plain text! No matter how often you change your JWT signing key and no matter if you added and "exp" claim: An attacker would have all the time in the world to write lovingly, hand-crafted and personally dedicated blackmail letters, he would even have enough time to make a mosaic of cut out newspaper-letters, like often depicted in the movies :-D Your NaughtyStore™ would actually finally get that notorious reputation, but not in the way you usually intended :-D Or imagine a video hosting site in a related business branch called Imagine the ids of the videos, which maybe just have to be put in a standard domain query string if you want to see them, like implemented in YouTube as described above. Since they could be correlated with a special user email in the JWT for example, they are associated with a user. Even if you usually don't care who knows your email since its maybe your public business email, the connection of your business email with these videos could make your palms quite sweaty if you see them in a blackmail. :-) The normal user and even a web developer can't estimate for sure which information an attacker has or not, but would neither like to find out or commit to law enforcement because of embarrassment. Perfect start for blackmail too. You already have the email, so you can actually write an automated script, where you can send a summary of the titles of the videos the said user watched, along with a nice introduction of yourself and the outstanding money she/he owes you... :-) Just pass your collected user data of maybe ten thousands of users into that script, lean back and wait who answers.. :-) If a page implements a mechanism like YouTube, where the information of the position of the progress bar get saved somewhere to the page when you leave, so you can actually continue the video were you left off, can be used for malicious intent in this case too. In isolation a handy and user friendly mechanism though. Now imagine the information where you left off gets saved additionally to the last twenty videos in the JWT of our "documentation site". So you now can not only see the last vids you watched, but also the exact moment when you left. All in all nothing special if it would be on a site like YouTube or any other site with rather "lame" content ( in terms of blackmailing ). But used for blackmail you can actually convince your victim you know "so much" about her/him, even the point in the video where his "user experience" was "satisfied". You can maybe easily bluff and use the last vid in a row of watched videos for this.. :-D If you want to get even more paranoid about a scenario like this, consider it will strike an important politician or the next candidate for presidency, which reputation can be annihilated with these information in the next electoral campaign. So JWT encryption could even decide about the course of history, if you want to see it this way. Haha.. :-D ConclusionSorry for that long comment and quite a lot of prose. Maybe I should extract that into a blog post, if it turns out my considerations weren't utter BS. :-) But had to get the thoughts out of my mind, which made me think, why encryption would be a good feature. :-) And for that I thought it's best to provide some lively examples in my opinion. While maybe a website which stores critical user data in the JWT and has an XSS vulnerability is maybe rare, it's still easily to imagine that these sites exist. I hope I didn't gave anyone stupid thoughts with my considerations to go "self-employed" in one or the other direction related to this comment, but I had to mention these use cases since all of the mentioned possible vulnerabilities could be easily avoided by just encrypting JWT by default. If somebody really knows what he is doing, he could just make it plain text on purpose then. What do you think about that? Wouldn't it be nice to have that feature in
Since both gems implement a method which lets user define their own payload
it isn't totally secure by default in a broader sense, since it allows users to put arbitrary data into the payload, also without even a clue in the docs that this could maybe lead to security implications. Clueless users could think themselves safe, since it says secure by default at the top and the Sure, Or am I maybe missing something about the mentioned, potential security implications and actually I'm just paranoid? :-D But i guess most of the mentioned issues make quite a sense.. :-) Possible SolutionI'm aware that adding encryption to it could add an additional threat and complexity since you have to drag in dependencies, but if you use a well-tested state-of-the-art library, things should work quite easily. The mentioned gem rbnacl also uses In order to make this work users could just define an # creates a "box" from a given key, that handles all
# the security considerations on its own by default
def box
key = ENV["DEVISE_JWT_ENCRYPTION_KEY"]
RbNaCl::SimpleBox.from_secret_key(key)
end
# before JWT gets dispatched
def encrypt(jwt)
return jwt unless config.encrypt_jwt # return the plain JWT if encryption is set to false
box.encrypt(jwt) # => encrypted JWT
end
# directly after JWT got pulled out of header
def decrypt(encrypted_jwt)
# return JWT without decrypting if encryption is set to false
return jwt = encrypted_jwt unless config.encrypt_jwt
box.decrypt(encrypted_jwt) # => decrypted and plain JWT
end Just a first, naive thought about implementing this. I don't expect an answer on that rather long comment. Just wanted to provide some "food for thought". But would actually be quite interested, what you or others think about it. :-) Thanks for reading :-) |
Wow, that was quite long :) Give me some time to read it with the meticulousness it deserves :) |
By now, if you need to go on with a project, it is easy to use something different than the id to encode/decode a user to/from the token. Just overwrite |
Of course take your time.. :-) Thanks for the clues, I had them somewhat in mind, since I already skimmed a bit over the documentation, but didn't find time yet to try it out actually, since in development working with plain ids is not a problem. But nice to know there are ways to change it later, so i can keep on using Will read more about Now i want to specialize in GraphQL API development so i needed a token based auth mechanism while still using devise. There are some other JWT based authentication gems, but they don't offer the functionality devise provides by far. If you like you can keep this issue open, and I can write about my findings of using devise with JWT and GraphQL. When I find out something new, I can maybe prepare a page for the wiki if I have smth. concrete. Right now it already works right out of the box, with a little tweaking for Actually i didn't found much so far about combining these technologies in rails, so there are maybe others who are interested in that topic too. There was quite a lot about how to use GraphQL with rails and simple authentication from scratch. But it would be quite a waste of resources to duplicate the functionality of devise, while there is already a well established library for it. And even more a gem like |
@christophweegen I've noticed a lack of recent content as well regarding this topic. My use case is simply building a side-project Rails 5 based API and wanted to explore encrypted JWT tokens --- security through obscurity being my main motive. I agree with your above post that an attacker could exploit the payload to determine resource numbers/id relationships. Any point of data could be useful depending on the situation. I don't have much to add to the conversation other than saying I'm a |
Hi, if ID autoincrement is one of your security / confidentiality concerns, take a look at UUID, and how they integrate well in Rails since Rails 5. |
Hey @kfrz, But take care, README says something about security implications by using hashids, one reason for that is they're rather short. But you can make them longer in config and don't forget to add a salt! So read carefully through the docs and follow the links where there is more background for using this gem. But if you make them bulletproof you should better go for a real encryption library. You can try out the Another things is, if you encrypt your payload, you want to most likely convert your encrypted payload to base64 again, otherwise your JWT isn't valid in terms of the specs anymore, since it will most likely contain special characters. Maybe it could get rejected by the JWT library when verifying. I'm not sure, didn't try it yet. Sounds for me like a rather quick and dirty workaround. I'm not so really convinced by all these possibilities yet. If we want to encrypt a JWT it should adhere to the JWE standard. There's already a gem for JWE. But since I didn't do much research in JWE yet, i can't give much information about it by now. But will stay on it. Maybe @waiting-for-dev can point to the method that have to be overwritten in devise-jwt where the full JWT gets dispatched and received, so we can exchange that for a JWE with the But will share my findings, sadly I'm rather busy at the moment. But a good starting point for getting to know more about jwt is maybe https://jwt.io/ where you also can get a free ebook about the JWT standard. ( JWE included ). If you got any questions, please let me know :-) |
Hey @fcatuhe, thanks for the link and mentioning that. I already thought about UUIDs too. Would also be a good solution to the problem. But it would add another level of complexity to a rather trivial problem ( at least in this case for me ). Working with plain integer ids is fine for me by now, there's no actual need to change the default way, since UUIDs solve problems i don't actually have and they provide functionality i don't really need (by now). :-) So it's a bit "shooting cannons on sparrows" ( german proverb ), which just means smth. like "overkill".. :-D Another downside of UUIDs they are rather long compared to plain integers, so if things are scaling (hopefully!) you clutter your database, if you don't actually need them. Maybe not such a big deal nowadays, but it still bugs me somewhat.. :-D I didn't spend a lot time researching on UUIDs yet. Maybe in future i will change my mind. :-) But good that you mentioned it here, since somebody could use that quite well in context of secure ids, especially if she/he already uses them. Having this said, and for not going totally astray from the devise-jwt topic of encrypted payload, smth. comes to my mind: How about providing two methods in devise that override the default JWT generation and verifying. So by just providing a, for example, "token" method ( which let's you choose whatever data you want to include in your I know there are already possibilites for that with warden strategies and so on. But wouldn't it be easier you would just have to define "token" and "user_from_token" in the User model or smth. like that? I'm not sure how easy to implement smth. like that would be, since I'm not knowing warden so well yet. @waiting-for-dev what do you think about that? Right now to create your customized JWT you have to patch at least two methods together. |
Some comments I think are key about everything that has been commented here:
Having said all of that, I think it is ok to add an optional layer of encryption to the token encoder/decoder system, because we are not covering the case where a responsible user wants to add some private information related with the authentication. I'll come back soon with implementation ideas to discuss it along what it has been already proposed. |
Hey @waiting-for-dev, totally agree with what you said in the last comment. Before using So in order that there is no misunderstanding:
Sounds good to me :-) Or did i forget/misunderstand something? So wrapping this discussion up: 1. Adding encryption functionality for JWT. Would really appreciate if you could add this. If I can do smth. to help, please let me know :-) Thanks a lot and best wishes to everybody here. :-) |
That's it @christophweegen 😄 Once I have a bit of time I'll dig into implementation options, but, of course, any help is welcome. Making encryption a default behavior is a possibility, but we have to weigh it with backward compatibility issues and performance. |
I still haven't had the time to study it in conscientiously, but I feel that the ideal way to go would be to solve this feature request in |
For now let's wait how the issue in If finally the feature is not integrated (surely we could help if some of us have the time :)), we could switch to json-jwt, which already integrates it, or simply use ruby-jwe from our end. |
Thanks! I've already read that JWE thread when searching for encrypted JWTs back then. I just have seen the last comment is from 2016. I don't think they will include encryption in in But the implementation is quite nice and easy to work with. Just dabbled a bit around with it in pry. On the other hand as far as i have seen, you can include headers in Wouldn't actually be needed for Do you actually need to include custom JWT headers for making I could open up a feature request for And do you need some functionality from Additionally I just had time to take a quick glimpse on |
It's true that the comment is old, but in a comment they say they want to integrate
I would say that what we need is symmetric encryption. If we want to send something to a client that we consider potentially unsecure, it doesn't make too sense to let it store the key to decode it. At least for the simple scenarios that I don't think we need anything special in the JOSE headers except for that it is standard in a JWE token.
Didn't have the time, neither. Quite busy at the moment... I recognize that I give it a low priority because I don't see it as very important (as I said usually you won't put something sensible in the payload -if ids are secret just tackle them in the origin), so any help with this will be very gratifying 😄 If people in I remember though that it should be implemented in |
I just added a new issue in |
Ok, wasn't quite sure how concrete the ideas in the comments were.
That's what i thought.
Didn't read about JWE specs yet, but I will let you know if i find smth. that could make sense.
You're right that it's not so urgent, since it's already easy to implement your own way right now. Maybe we just take some time too and think about it and there will be an even better solution.
Sounds great. Will follow the progress in the link you provided. :-) |
I'm going to close it, because, as discussed, I don't see the reason why this should be implemented (the only acceptable use case I see can be better solved with UUID), and it would add complexity to the library. Anyway, if somebody is willing to take it I would reopen it. |
Hello there!
Is there a way to encrypt the JWT payload or even the whole JWT, so claims ( especially
"sub": user_id
or other consecutive ids or claims that are maybe business secrets ) can't be seen by the user when inspecting the token, since it's only signed but not encrypted?Right now a simple base64 decode reveals all claims in the payload ( plain default JWT behaviour of course)..
A solution would be maybe something like a callback or similar:
Even better just a config option where you can maybe choose:
Sadly my knowledge of devise internals isn't this deep (yet), otherwise i would work smth. out and make a pull request, but i will keep digging in it for sure:-)
Maybe other people could use this functionality as well.
Thanks in advance and thanks for providing this already great gem which provides a lot of value for me :-)
P.S: Today i dabbled with a gem called
rbnacl-libsodium
in combination withdevise-jwt
and always got an error when loadingrails server/console
but not in plain-old consoleIrb/Pry
. After some time figured out thatrbnacl-libsodium
must be put beforedevise-jwt
in the Rails Gemfile. Maybe you could put that into docs since other maybe encounter that problem too.Thanks and keep up the good work! :-)
The text was updated successfully, but these errors were encountered: