waiting-for-dev / devise-jwt

JWT token authentication with devise and rails
MIT License
1.25k stars 129 forks source link

JWT encryption #67

Closed christophweegen closed 6 years ago

christophweegen commented 6 years ago

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:

def before_jwt_dispatch(jwt)
  encrypt(jwt)
end

def before_jwt_verification(encrypted_jwt)
  decrypt(encrypted_jwt)
end

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 with devise-jwt and always got an error when loading rails server/console but not in plain-old console Irb/Pry. After some time figured out that rbnacl-libsodium must be put before devise-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! :-)

waiting-for-dev commented 6 years ago

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.

P.S: Today i dabbled with a gem called rbnacl-libsodium in combination with devise-jwt and always got an error when loading rails server/console but not in plain-old console Irb/Pry. After some time figured out that rbnacl-libsodium must be put before devise-jwt in the Rails Gemfile. Maybe you could put that into docs since other maybe encounter that problem too.

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?

christophweegen commented 6 years ago

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 devise-jwt.

But the hint to warden-jwt_auth gave me a good starting point where to search for that. Didn't consider that the actual token system sits below devise in warden, which i read about once actually, but remembered it after you pointed me to it.

A short glimpse at the warden-jwt_auth docs already gave me a first idea how to solve the problem of the hashed/encrypted sub claim. When i figure out a solution I'll let you know, so if you like you can put it in the warden-jwt_auth wiki or similar.

Other examples of important use cases for an encrypted JWT

One 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 jwt-devise, since it says "secure by default". ( More thoughts on that later ). The actual effect on security is of course always depending on the use case, which i will share my thoughts on a bit further down too.

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 Thoughts

A 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 post request for creating a new shopping cart item, the old JWT gets revoked and overwritten by an updated JWT from the response including the new shopping cart content.

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™ Example

Now consider you're shopping at www.really-naughty-store.example.com and you have stuff in your shopping cart that you better wouldn't tell your neighbours about.

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 www.blackmail.naughty-advertiser.example.com

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 Authorization: Bearer *** header and could pose as me and see the stuff I have in my shopping cart." That's true.

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 devise-jwt does this already by default ). So a possible attacker would have to hurry using the stolen tokens to extract all that delicate information from your user pages before you change the signing key or the "exp" claim runs out.

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 www.triple-x-documentaries.example.com which stores the 20 last watched "educational videos" in your JWT. Quite user friendly if you can "continue" where you "left off", developer friendly too since nothing gets stored in the database. :-D

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

Conclusion

Sorry 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 warden-jwt_auth to include encrypted payload, for example by a config option and possibly make it even default? This would contribute well to each of the first sentences in warden-jwt_auth and devise-jwt repo too:

It follows secure by default principle.

Since both gems implement a method which lets user define their own payload

User records may also implement a jwt_payload method, which gives it a chance to add something to the JWT payload:

def jwt_payload
{ 'foo' => 'bar' }
end

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 jwt_payload method available without any annotation.

Sure, jwt_payload is quite secure if you leave it just default, but not secure by default, in case you're changing it. People could get into real trouble because of this little, easily misunderstood ambiguity. :-)

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 Solution

I'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 secure by default principle and providing an easy API for secure encrypting and decrypting, which is called SimpleBox.

In order to make this work users could just define an ENV["DEVISE_JWT_ENCRYPTION_KEY"] through rails secret and three methods of code could be added at the appropriate places with a little config check in them if the user has set the encryption of the jwt to false:

# 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 :-)

waiting-for-dev commented 6 years ago

Wow, that was quite long :) Give me some time to read it with the meticulousness it deserves :)

waiting-for-dev commented 6 years ago

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 find_for_jwt_authentication class method and jwt_subject instance method in the model. See its current implementation.

christophweegen commented 6 years ago

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 devise-jwt. :-)

Will read more about warden too, by now I just used the "normal" devise and it provided all the functionality i needed so there was no urgency for messing around with warden. I usually like to keep things default since it reduces the chance for unforeseen bugs. And when it works, it works :-)

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 rails new my_api --api as described in the docs, except for said encryption functionality ;-)

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 devise-jwt which actually provides already all the functionality you need for API auth. Sadly i didn't find any GraphQL tutorial that used this gem or linked to it. Most of them were actually 2-3 years old. Strange.. :-/

kfrz commented 6 years ago

@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 devise user who is interested in this.

fcatuhe commented 6 years ago

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. A recent article here for example.

christophweegen commented 6 years ago

Hey @kfrz, if you just want to obfuscate your ids then you can use hashid-rails and combine it with the things mentioned by @waiting-for-dev https://github.com/waiting-for-dev/devise-jwt/issues/67#issuecomment-366174139

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 rbnacl-libsodium gem i mentioned in https://github.com/waiting-for-dev/devise-jwt/issues/67#issuecomment-366067525. At the bottom is an example how to use it maybe. But depends on your use case.

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 jwe gem. Maybe a cleaner solution and more adhering to the JWE specs.

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 :-)

christophweegen commented 6 years ago

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 Authorization: Bearer *** response header after sign in and a "user_from_token" method which takes an arbitrary token and returns the current user could be a handy addition so users could implement their own way of using tokens if they want to.

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. jwt_subject and jwt_payload. But it's not easy to define your own token, without using JWT at all, if you would like to, for example storing an (encrypted) UUID as user_id. I know this would have nothing to do with JWT anymore then, but wouldn't it provide additional value to devise-jwt in terms of customization..

waiting-for-dev commented 6 years ago

Some comments I think are key about everything that has been commented here:

[...]

[...] When revoking a JWT token there is no need to store the whole token in the database. As it contains readable information, we can, for example, extract its jti claim, which uniquely identifies it. This is a huge advantage, because it means that stored information is completely useless for an attacker. Therefore, there is no need to hash it to accomplish a good zero-knowledge policy, and there is no need to keep a salt value for each user to protect us from rainbow table attacks.

Now about the alleged overhead that JWT with revocation would suppose. As we said, with JWT we have to take two steps: signature verification and a server query. In opaque tokens, instead, it seems we just have to query the server. But last is not true. A secure opaque token implementation should not store unencrypted tokens. Instead, it should require the client to send a kind of user uid along with the unencrypted token. The user uid would be used to fetch the user and the unencrypted token would be securely compared with the hashed one. So, this hash comparison is also a second step which, even I haven’t benchmarked it, should have a similar overhead with signature verification.

Using a standard like JWT has also some abstract benefits which are difficult to measure. For example, usually, with current libraries, you get for free an integrated expiration management through the exp claim. However, as far as I know, there is no standard for opaque tokens, which make libraries prone to reinvent the wheel every time. In general, using JWT should be more portable.

[...]

Don’t add private information unless you encrypt your tokens. Bare JWT (without encryption) is just a signed token. It means that the server perfectly knows whether it issued the incoming token or not. But the information contained in the token is readable by everyone (try it in ‘Debugger’ section on JWT site); it is just base64 encoded. So I recommend just coding harmless information like the user id.

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.

christophweegen commented 6 years ago

Hey @waiting-for-dev,

totally agree with what you said in the last comment. Before using devise-jwt and opening the issue i already read all of your blog posts about it. We're quite informative and i really liked them. :-)

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. 2. Optional: Make encryption of JWT default behavior.

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. :-)

waiting-for-dev commented 6 years ago

That's it @christophweegen :smile:

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.

waiting-for-dev commented 6 years ago

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 ruby-jwt so we can just use its API without reinventing the wheel.

waiting-for-dev commented 6 years ago

For now let's wait how the issue in ruby-jwt evolves. I added a comment there. This way we could easily dispatch a JWS or a JWE depending on a configuration parameter.

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.

christophweegen commented 6 years ago

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 ruby-jwt since there is already ruby-jwe, which seems to be working but for example doesn't provide a method for including custom JWT headers and it only returns the decrypted payload but no JWT headers.

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 json-jwt but it only supports asymmetric encryption. Quite clumsy to work with IMHO. You need 2 keys, if you want to sign and encrypt your JWT.

Wouldn't actually be needed for devise-jwt or am I seeing smth. wrong? Since ruby-jwe provides encryption easily with just defining one key it seems to be the more suitable solution, if you don't need to include custom JWT headers.

Do you actually need to include custom JWT headers for making devise-jwt work?

I could open up a feature request for ruby-jwe if so.

And do you need some functionality from ruby-jwt that ruby-jwe doesn't provide? Didn't had time yet to compare both gems in terms of functionality.

Additionally I just had time to take a quick glimpse on ruby-jwe and json-jwt gems, so i guess it would be better if you could confirm my conclusions.

waiting-for-dev commented 6 years ago

I don't think they will include encryption in in ruby-jwt since there is already ruby-jwe, which seems to be working but for example doesn't provide a method for including custom JWT headers and it only returns the decrypted payload but no JWT headers.

It's true that the comment is old, but in a comment they say they want to integrate ruby-jwe interface within ruby-jwt. If they are still willing to do it, maybe we could help there and just update ruby-jwt dependency version here.

On the other hand as far as i have seen, you can include headers in json-jwt but it only supports asymmetric encryption. Quite clumsy to work with IMHO. You need 2 keys, if you want to sign and encrypt your JWT.

Wouldn't actually be needed for devise-jwt or am I seeing smth. wrong? Since ruby-jwe provides encryption easily with just defining one key it seems to be the more suitable solution, if you don't need to include custom JWT headers.

Do you actually need to include custom JWT headers for making devise-jwt work?

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 devise-jwt covers, with no specialized authorization server.

I don't think we need anything special in the JOSE headers except for that it is standard in a JWE token.

And do you need some functionality from ruby-jwt that ruby-jwe doesn't provide? Didn't had time yet to compare both gems in terms of functionality.

Additionally I just had time to take a quick glimpse on ruby-jwe and json-jwt gems, so i guess it would be better if you could confirm my conclusions.

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 :smile: If people in ruby-jwt doesn't give feedback maybe we can just go with ruby-jwe ourselves, given that json-jwt has the problems you mentioned.

I remember though that it should be implemented in warden-jwt_auth. I would wrap ruby-jwt and ruby-jwe in a common interface and inject the one that is needed in TokenDecoder and TokenEncoder.

waiting-for-dev commented 6 years ago

I just added a new issue in warden-jwt_auth. Just continue the conversation there and leave this issue here open until we integrate the feature created there.

christophweegen commented 6 years ago

It's true that the comment is old, but in a comment they say they want to integrate ruby-jwe interface within ruby-jwt. If they are still willing to do it, maybe we could help there and just update ruby-jwt dependency version here.

Ok, wasn't quite sure how concrete the ideas in the comments were.

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 devise-jwt covers, with no specialized authorization server.

That's what i thought.

I don't think we need anything special in the JOSE headers except for that it is standard in a JWE token.

Didn't read about JWE specs yet, but I will let you know if i find smth. that could make sense.

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 smile If people in ruby-jwt doesn't give feedback maybe we can just go with ruby-jwe ourselves, given that json-jwt has the problems you mentioned.

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.

I remember though that it should be implemented in warden-jwt_auth. I would wrap ruby-jwt and ruby-jwe in a common interface and inject the one that is needed in TokenDecoder and TokenEncoder.

Sounds great.

Will follow the progress in the link you provided. :-)

waiting-for-dev commented 6 years ago

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.