jwtk / jjwt

Java JWT: JSON Web Token for Java and Android
Apache License 2.0
10.17k stars 1.32k forks source link

No way to read body, and then later verify a token #86

Closed chadjaros closed 6 years ago

chadjaros commented 8 years ago

I want to have a signed token with a subject claim. I can create the token perfectly well.

When I'm verifying the token, I would like to first determine the subject. After determining the subject and retrieving some data, I would like to verify the signature.

I can do the parsing of the body manually, but it would be nice to have this as a function of the parser class

lhazlewood commented 8 years ago

What's wrong with using a SigningKeyResolver?

https://github.com/jwtk/jjwt#04

chadjaros commented 8 years ago

I'd rather use a method like:

public Map<String, Object> parseBody(String token) {
    String json = new String(BaseEncoding.base64().decode(token.split("\\.")[1]), "UTF-8");
    return objectMapper.readValue(json, Map.class);
}

than create a subclass implementation. For my use case, ideally I would be able to run the token signature through a validator without also parsing the body, and a parser without running validation. This would allow me to construct an appropriate workflow.

lhazlewood commented 8 years ago

I'm confused - there is no subclass implementation, and you have the ability (today) to perform full inspection before the signature is validated.

What is the use case exactly? From a JWT library implementor's perspective, it is dangerous to allow parsing without token validation at all - it opens up security holes, so I'm trying to understand why this should be in our default API?

chadjaros commented 8 years ago

The use case is that the token will contain a user-id. This user id is required data in order to retrieve the secret from the database, which is stored per-user. SigningKeyResolver is an appropriate hook for this, but I'd be hesitant to embed a data dip within a third party library.

I'd have to create a subclass of SigningKeyResolver or SigningKeyResolverAdapter in order to properly leverage this hook, and I'm kind of intimidated by both of these options.

vertex-github commented 8 years ago

@lhazlewood I took a quick look at the SigningKeyResolver example you referenced. That mechanism still requires us to generate a key and pass it back to jjwt. In our use case, we dont want to validate the key at this point - just extract a couple of the claims fields. While we can wrap the following call with an exception block and move forwards, its not overly elegant: Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(resolver).parseClaimsJws(jwt);

we understand why you would want to enforce the verification, but there are certain operations that we can perform on the web server before we incur the cost of going to our auth service (which is an additional round trip network hop) to validate the tokens authenticity.

ghost commented 8 years ago

I hit the same wall. I need what's inside the token to get my key, can't do it.

lhazlewood commented 8 years ago

@rickla I don't understand:

I hit the same wall. I need what's inside the token to get my key, can't do it.

This is exactly what the SigningKeyResolver was designed for. Can you please elaborate?

ghost commented 8 years ago

I put in the key resolver but it's a kludge. My utility is wrapping this and I have caller wanting just the payload and they don't yet have a key.

So what I did was put in the resolver, save the data before the exception, then return the data to my caller.

To hook in the key generation into this would not fit our model. The key generating code we don't want other things calling into, it's walled off.

So I'm fine but it's just not pretty code. I think it's a legitimately useful utility to simply get the payload, so a nice to have.

lhazlewood commented 8 years ago

Yep, I'm not saying it's not a valid issue - I just didn't understand the initial comment :) Thanks for clarifying!

lhazlewood commented 8 years ago

@vertex-github @chadjaros and @rickla

In our use case, we dont want to validate the key at this point - just extract a couple of the claims fields.

This is exactly what I'm confused/concerned about, and it seems that each of you are trying to do similar things.

If a JWT is signed (has a signature), and you don't validate it, you cannot trust what is in the JWT at all. You shouldn't even look at it, process it, whatever. If you ignore this, you open yourself up to spoofing attacks where an attacker can create any 'ol JWT they want and you still process it successfully. Are you actually ok with that? (Any context would be helpful to understand why you'd be ok with that :) ).

If you're worried about performance concerns of hitting the data store, that's what caching is for, no? Reducing data store roundtrips is an orthogonal concern to signature validation, and using caching (as just one approach) alleviates this concern.

Here's an example of what I don't see as particularly difficult:

The SigningKeyResolver is created for the exact reason of extracting the key needed to validate the JWT. Ideally, you should have an implementation of this interface that looks at something in the claims or header that allows you to interact with a data service of some sort to get the key. For example:

//Your 'MyKeyDao' implementation interfaces w/ your data store, and probably
//even uses caching (hopefully?)
final MyKeyDao dao = getMyKeyDao()
SigningKeyResolver keyResolver = new SigningKeyResolver() {
    @Override
    public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
        //inspect the header or claims, lookup and return the signing key:
        String keyId = header.getKeyId(); //or any other field that you need to inspect
        return dao.getKey(keyId).getBytes();
    }
};

parser.setSigningKeyResolver(keyResolver)....

Instead of making it an anonymous inner class, you could make it a normal class (and pass in your DAO in the constructor, for example) if it make it easier to use, for example:

final KeyDao dao = getKeyDao();
SigningKeyResolver keyResolver = new MySigningKeyResolver(dao);
parser.setSigningKeyResolver(keyResolver)....parseClaimsJws(aJws);

This pattern - of using a bridge interface between JJWT's needs and your data services - is particularly elegant. It ensures loose coupling, high cohesion, etc, without being invasive to your API at all.

Does this make sense?

Hopefully this helps clarify my concerns. I'm looking forward to reasons why this isn't sufficient given the security implications. I haven't yet heard a scenario where it would be ok to successfully process (even in part) JWSs that could be spoofed. I'm not saying there aren't any, but I hope you guys can help me understand them.

Thanks!

vertex-github commented 8 years ago

Sure - one of the fields in the token is the expiration date. If the token is expired, then we dont send the token off for validation which incurs a network round trip. If its still valid, then we proceed, validate etc. There are also other fields that we want to leverage that we don't care about from a security standpoint - some operations just don't require validation in our application. For those that do, we validate the integrity of the key and incur the round trip to our auth service.

chadjaros commented 8 years ago

If a JWT is signed (has a signature), and you don't validate it, you cannot trust what is in the JWT at all.

If your JWT contains the caller's id, and you require the callers id to find the signing key, you have to trust the unvalidated JWT up to a point. This is why you provide the SigningKeyResolver, so that you can initiate that lookup and validation.

I think the pattern is good, but the implementation leaves something to be desired. My issue remains that the requirements of implementing a SigningKeyResolver (returning a Key object, having two possible methods to implement) is more complex than just parsing out the body manually and using it to resolve the key. If SigningKeyResolver had a single method, provided a single JsonNode as a parameter, and returned a String, I would be much more likely to use that pattern.

ghost commented 8 years ago

For my case it contains the user id. That is used to get the key. Then we validate the token, no security hole there. If the userid is tampered with, nothing works.

The implementation problem is it assumes my code containing jwt processing is aware of its callers, which it is not. I can't have my generic jwt utility service magically aware of my login system, nor do I want to pass callback pointers in, it's just messy.

lhazlewood commented 8 years ago

My issue remains that the requirements of implementing a SigningKeyResolver (returning a Key object, having two possible methods to implement) is more complex than just parsing out the body manually and using it to resolve the key. If SigningKeyResolver had a single method, provided a single JsonNode as a parameter, and returned a String, I would be much more likely to use that pattern.

Unless I'm missing something, I think the SigningKeyResolver interface is already better than what you propose: it is a single method, not two. Additionally, what is presented to you in the callback method is fully parsed and ready-to-use Java objects that reflect both the header and the claims, and you can inspect them without restriction. No JsonNode messiness. Also, why a String return value? Signatures are computed with byte arrays, not Strings; Strings are ambiguous - it is not easy/efficient to determine if the String is a base64-encoded byte array or if you want to use the actual string bytes as the key;

Using an anonymous inner class or a standard 'plain ol pojo' instance to 'plug in' behavior is, IMO, a particularly elegant way to enable this in a concise manner in Java. I don't see how it can get any better/easier without opening security holes. I think it is a nice solution actually - I bet the amount of code you'd write is almost identical, with maybe a couple of lines total difference. This is a small price to pay IMO for guaranteed security assertions. The alternative of opening something so insecure to a large user base is much riskier and fraught with peril (or at the least, increased support load for the JJWT devs).

Perhaps a code example showing how much better it would be with an alternative approach would be helpful here? It is hard for me to visualize (and realize) that things are easier/better easier without real code. I ideally would like to come to an agreement if possible! :)

lhazlewood commented 8 years ago

@rickla

For my case it contains the user id. That is used to get the key. Then we validate the token, no security hole there. If the userid is tampered with, nothing works.

Just to ensure I really understand here: you believe this is messy?

Jws<Claims> jwt = parser.setSigningKeyResolver(new SigningKeyResolver() {
    public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
        String userId = getUserId(claims);
        return keyStore.findByUserId(id).getBytes();
    })
    .parseClaimsJws(aJws);

I think that the above is rather nice - even nicer with a dedicated class to reduce the lines of code (and easier to test).

lhazlewood commented 8 years ago

@vertex-github would your concern be addressed if we did the other validations first before doing the signature validation? Currently that happens after signature validation, but re-ordering it would be trivial.

ghost commented 8 years ago

That example is bad for my particular use. keyStore.findByUserId(id) is not a call I can let the code where this lives access. That's an isolation breakage for me.

Here's my code right now. Yep ugly! But safe and contained, only calls in by requestors, no callbacks.

public Claims decodeToken(String token)
    {
        Claims claims = null;

        try
        {
            // This hacky thing is to allow getting the claims without caring about the signature.

            Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
                @Override
                public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claimz) {
                    claims = claimz;
                    return "dummy".getBytes(); //implement me
                }})
            .parseClaimsJws(token);
        }
        catch(SignatureException e)
        {
            // Don't care in this case
        }

        return claims;
    }
vertex-github commented 8 years ago

It would be great if there was a simple way for us to extract the body contents so that we could make decisions on the web server side of our back end infrastructure. Only our auth service holds the keys that are used for signing and validation - they do not get passed out. So, by forcing key validation when reading a signed token on the webserver, you force us to a.) right some messy code and catch/ignore a bunch of exceptions just so we can get at certain claims values or b.) Incur a round trip network cost to validate and parse the token (which we'd rather not do until we know we have to).

We have a working solution right now (option (a) ) but we think it would be nicer if we could use a simpler API to get at the body contents with the understanding that we think we know what we are doing with respect to the security of our application. If we choose to not validate the key before extracting parts of the body content then that's our choice. I understand your standpoint with respect to reading valid keys though. We do ultimately use the key validation but only when we deem that we need to do so.

lhazlewood commented 8 years ago

Thanks @rickla

I understand that you're trying to get the claims without validation, but why. Why can it be trusted?

Any maybe more broadly, why should JJWT, a generic library used by hundreds of teams and/projects, many in very secure contexts, allow what is essentially violating the JWT specification?

Could this be easily solved in your particular use case(s) by doing the following?

int i = jws.lastIndexOf('.')
String withoutSignature = jws.substring(0, i+1);
Jwt<Header,Claims> untrusted = Jwts.parser().parseClaimsJwt(withoutSignature);

This achieves what you want, and is better self-documenting to others in your project that you are explicitly side-stepping the JWT specification for your own needs - a calculated approach.

vertex-github commented 8 years ago

@lhazlewood Thats exactly what I think we're all trying to do - but a single API method would be much nicer: call it something like parseClaimsUnsecure( String signedToken ) or similar

ghost commented 8 years ago

I tried removing the signature and the code throws and exception not allowing it.

I think the jwt is being misinterpreted. If you had data that should not be used in any way unless verified, you'd encrypt it.

I have to have some way to know where to get my key. I could add some additional header or parameter with the user id, but that makes no sense to me. It belongs inside the token.

Finally, the data I am using for userId can be trusted because I validate it with a signature! In no part of my code do a trust it until then. Using the userId to pull out a possible key is not my definition of trust.

lhazlewood commented 8 years ago

@vertex-github my concern here isn't that you guys do/do not know what is good for your project - I'm sure you do, and make very calculated decisions here.

My concern is in adding something so risky that knowingly violates the specification (when JJWT is first and foremost supposed to be an uber-easy-to-use spec-compliant library), especially when compliance is relied upon by many other developers, in the name of convenience.

You guys have to remember that there are tons of JJWT users that haven't a clue about security or best practices, etc. If we make it easy to shoot yourself in the foot, they will (and inherently blame JJWT, which doesn't do us any good or foster good will).

Given this, and that there is a very nice/clean 3 line example that you could use to achieve your goal, and, I would argue, forces you to self-document (make it obvious) to other developers in your project that you're making a very calculated decision to violate the spec, do you still think this warrants a change to JJWT?

lhazlewood commented 8 years ago

@rickla I just ran this test and it works just fine (no exceptions):

byte[] key = MacProvider.generateKey().getEncoded();
String id = UUID.randomUUID().toString();

String jws = Jwts.builder().setId(id)
        .setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
        .claim("state", "hello this is an amazing jwt").compact();

int i = jws.lastIndexOf('.');
String untrustedJwtString = jws.substring(0, i+1);
Jwt<Header,Claims> untrusted = Jwts.parser().parseClaimsJwt(untrustedJwtString);
lhazlewood commented 8 years ago

I think the jwt is being misinterpreted. If you had data that should not be used in any way unless verified, you'd encrypt it.

This is verifiably false in cryptography. Encryption as a general concept does not ensure authenticity at all.

There are many cases where message content can be publicly seen by 3rd parties with little/no ill effect, and that's ok, but you might still need to ensure the message is authentic before processing it. A national passport is one such example.

lhazlewood commented 8 years ago

So, here's my next question.

If we add this to the parser as an insecure method or something like that. How would it work?

Signature validation is just one of many validations. Should that only be turned off? What about exp, nbf validation, or other types of validation? Is it an all or none thing? Thoughts?

ghost commented 8 years ago

@lhazlewood I really mis-stated that, I'm well aware the two are different things!

Anyway, I figured out why your example works! I was using parseClaimsJws not Jwt I must admit I am not sure why two calls, but I just blindly copied it (I have not found the javadoc yet).

Anyway, this is much cleaner and exactly what I was looking for! It's probably what the original poster wanted also.

lhazlewood commented 8 years ago

@rickla yeah, once you remove the signature at the end, it is no longer considered a JWS, hence the method name change to parseClaimsJwt

The difference in method names exists to make it easier to provide a return value of the type you expect to use. A plaintext JWT is different than a claims JWT (e.g. body string vs body map respectively). Because of generics and subtypes, we need to scope the return value to be an expected type (i.e. a Jws has a getSignature() method, a Jwt does not). HTH!

lhazlewood commented 8 years ago

P.S. If you have suggestions for how this could be made more obvious/intuitive, please let us know! The trick is ensuring that security and type-safety are maintained at the same time ;)

ghost commented 8 years ago

I am new to these and the distinction between at jwt and jws is confusing, mainly because I thought jwt.io was a good learning source, and apparently they are a bit off, as well as other sites. It led be to believe jwt was the three part format, but it's really just the payload in the middle, right?

To help maybe the exceptions could say "this token has a signature, use parseClaimsJws instead" and vice-versa. That would have fixed me up!

lhazlewood commented 8 years ago

Yeah, the spec doesn't make it much easier to understand. There is 'jwt' which is either a non-signed JWT, or, commonly, the generic name for any of the formats. Then there's jws, which is a signed jwt, and then a jwe, which is an ecrypted jwt. Then there's jwa, the spec for the various crypto algorithms used in jws and jwe. Not too easy to grok :/

At a high level, a plain JWT has (in order): header, period, body, period. A JWS has: header, period, body, period, signature a JWE has: header, period, optional encrypted key, period, initialization vector, period, ciphertext, period, authentication tag

And what is in each differs depending on the algorithm used. HTH!

lhazlewood commented 8 years ago

@vertex-github do you think we need to re-order the validation checks and interact w/ the SigningKeyResolver last?

If we did that, would it have solved your specific problem? Or do you still need to look at the claims for something else?

vertex-github commented 8 years ago

Not really - we thought a simple API method that could give us the Claims without us having to mess with keys would be nice.

ghost commented 8 years ago

How about this, make a little utility call somewhere JwsToJwt doing that string truncation to remove the signature. Then use the Jwt to get the claims.

lhazlewood commented 8 years ago

@vertex-github I'm trying to address your previous comment that you wanted exp and such to be validated before incurring a network call for signature validation. This is orthogonal to disabling validation. I'm just trying to be clear on your intentions on that specific comment.

lhazlewood commented 8 years ago

@vertex-github you just repeated your previous comment. It did not answer my question.

Your stated concern was that you didn't want to incur a network call until after the dates have been validated. If we moved date validation before signature validation, this would solve that problem. What other concern do you have that is not satisfied by this change?

I'm not being difficult here - as a library maintainer, I need to know the use cases so I can support them effectively. A use case of 'because I like it that way' isn't enough to warrant building in breaking behavior into a specification-compliant library.

vertex-github commented 8 years ago

Huh, so we did - my apologies. That wasn't our intent. We totally appreciate we are likely in a minority situation here so . Thanks for your hard work on the library. Just leave it as is.

lhazlewood commented 8 years ago

Thanks for the reply. But per your comments, I do think it is better to do the other validation before signature validation anyway - specifically because of the potential for a network hop. It's a minor optimization, but I'm sure it could help!

lhazlewood commented 8 years ago

@vertex-github I re-read this today:

It would be great if there was a simple way for us to extract the body contents so that we could make decisions on the web server side of our back end infrastructure.

What I am about to write is a large tangent for the originally discussed ticket, but I think it could be helpful for other people considering similar approaches.

If I'm reading between the lines, I think I understand what might be going on: the same JWS used to authenticate a request into infrastructure is then forwarded on to backend web servers where they in turn look at this same JWS as well for relevant information. Those services/microservices inherently trust the JWS - they don't need to authenticate the token again.

If this is the case, I think the appropriate thing to do in scenarios like this is - especially if being specification compliant and security conscientious is meaningful - is to not use the same JWS:

Something that does the token validation - an API Gateway server (like Zuul or whatever) - or a servlet filter or similar - would validate the JWS because it 'sits higher' in the network stack and/or has direct access to your identity management system that can validate the signature. That component, after validating the JWS, should probably create a new unsigned JWT (not a JWS) that contains the information you need (even if it is a re-packaging of the aforementioned-but-validated Claims), and that new JWT can be sent to backend services (Header, cookie, whatever) and they can inherently trust the content in the token (and not treat it as a JWS, because it is not).

In environments where this approach would be seen as risky (i.e. backend services implicitly trusting JWTs from an API Gateway), other approaches for this is that the gateway server/component/device/whatever translates the already-authenticated JWS into a new JWS that is signed with a key that the backend services have easy access to (cached, in-memory), and they use that key to assert the JWS signature created by the gateway.

IF the original use case is to re-use a JWS in the network architecture everywhere and just ignore signatures in backend services, I have to confess that it is IMO a particularly risky endeavor and not something I would want to make it easier to do w/ JJWT - it's almost as if JJWT's api design is elucidating a scenario that might be identified and fixed instead of breaking specification-compliant behavior in the name of convenience. Granted, the above use case may not be at all what was originally discussed, and in that case, I'm sorry for the tangent :)

I fully understand that you may totally disagree with this viewpoint - I'm just adding it to the discussion here in case others find value in it :)

khalid64927 commented 8 years ago

@lhazlewood I'm getting this exception

"IllegalArgumentException: The default resolveSigningKey(JwsHeader, Claims) implementation cannot be used for asymmetric key algorithms (RSA, Elliptic Curve). Override the resolveSigningKey(JwsHeader, Claims) method instead and return a Key instance appropriate for the RS256 algorithm."

I'm wondering how can I extract the key value from the certs in JwsHeader in resolveSigningKey method so that I can verify the signature?

@Override public Key resolveSigningKey(JwsHeader header, Claims claims) { return getSigningKey(header, claims); }

The JWS header looks like this as below

{"alg":"RS256","x5c":["certString1","certString2"]}

smileatom commented 8 years ago

There is absolutely nothing insecure about reading a JWT to extract a "potentially valid" claim. The signature is either valid or invalid. You cannot trick an application into accepting a signature, since the contents are valid and owned by the signer as proved by the signature itself. If it were insecure in any way to transmit this data which is easily readable in base64, JWT would not be suitable for the representing claims at all. In fact IF THE SIGNATURE passes, you KNOW the data is trustworthy, so how can reading that data 10 milliseconds before validating the signature be insecure. Makes no sense. Cart, meet horse.

I should mention, this isnt the only misguided approach in java JWT libraries. Simply base64 decode the JWT yourself and then use the metadata if you need to and validate later. Although honestly when I see libraries like this, borne of misguided thinking, I think relying on them at all is probably not a great idea. The author would have you believe that the debugger on JWT.io should not be possible because decoding the JWT is inherently insecure. This library wouldnt allow you to build such a useful, secure tool.

lhazlewood commented 7 years ago

@rendion what are you talking about? Don't put words in my mouth - no one is saying tools shouldn't be possible due to invalid signatures. You can use JJWT today to do what was asked for above, it's just maybe not as easy as it could be, and that's a calculated decision because JJWT (and the RFC) favors security. It's certainly not 'misguided thinking' - it was in fact 'calculated thinking' - you just may choose to favor convenience over security, which is a preference.

What is important here is that a (good) JWT library MUST indicate (as per the RFC) that the token is invalid, indicating that it absolutely shouldn't be used per the specified context. For example, just because a signature is valid, but exp has expired, the token MUST NOT be used in the originally intended context. This is in the RFC - it's not up for debate.

There is absolutely nothing insecure about reading a JWT to extract a "potentially valid" claim.

This statement is just plain wrong. There's no other way to say it.

It is not trustworthy (as you said it was), because the context is what matters here - not just the signature (which is part of the overall context). Those with security backgrounds know this - you can't accept some security controls and dismiss others - security doesn't 'work that way'.

Now, if the app developer chooses to ignore the invalid indicators, they can do so (to create tools for example), but this opens a scope of problems that could impact security, depending on what one is trying to do. It is often risky to do so, and JJWT can't know about your specific 'I don't care, I'll ignore the spec' use case, which is why the JWT RFCs (and JJWT by association) make it crystal clear when you're doing something that violates the spec (and security controls). You can then use that information to do something else, but at your own peril.

I can't tell you how many bugs and security breaches I've seen over the years because people like to interpret security their own way in the name of convenience. My last comment above was about best practices to avoid these problems.

Just because you may not understand this or disagree with it doesn't mean that this is 'misguided thinking' - that's just rude and disrespectful. Based on your github profile, it looks like you may not have much experience in development or security, so I'll chalk up your comments as 'misguided thinking' (to use your own words).

alexcase52 commented 7 years ago

I don't see any problem reading properties of not validated jws if it is needed and then validate. With your resolver-approach it is also possible but a little bit more tricky because the control is inverted.

ok, the "secure" api call is Claims claims = Jwts.parser().setSigningKey(jwtKeyProvider.getKey()).parseClaimsJws(token).getBody();

The insecure API might look like:

InsecureParser insecureParser = Jwts.parser().disableSignatureCheck();
Claims claims = insecureParser.getBody();
// select proper validation key based on claims
SecureParser secureParser = insecureParser.setSigningKey(properKey);
secureParser.checkSignature();
peterjurkovic commented 7 years ago

I don't neither. There is a few use cases when a claim needs to be checked before checking signature. E.g. based on the claims deiced which Auth service handles authentication. Would be nice to have this.

bpappin commented 7 years ago

TL;DR

didnt read most of that anyway, but being able to get the claims without validating the signature is vital for us, since we do not want to distribute the signing key, ever. The client still need to be able to get at the unencrypted claims.

The only code that cares is the server side anyway, since we are validating ourselves with the server. The client code should not have the signing key, otherwise it can be compromised, and the server can no longer trust the token.

dogeared commented 7 years ago

@alexcase52 - I don't understand how your solution improves on the existing one. Your comment:

// select proper validation key based on claims

is exactly what you can do inside the signing key resolver:

SigningKeyResolver signingKeyResolver = new SigningKeyResolverAdapter() {
    @Override
    public Key resolveSigningKey(JwsHeader header, Claims claims) {
        // select proper validation key based on claims ^^
    }
};

Claims claims = Jwts.parser()
    .setSigningKeyResolver(signingKeyResolver)
    .parseClaimsJws(token)
    .getBody();
jong64 commented 7 years ago

Being able to read the claims is NOT the same thing as trusting the claims. I re-read the RFC several times, and it in no way prohibits application from being able to read the claims. The only thing that truly matters is that it can NOT trust the claims it just read UNTIL the signature validation succeeds.

We can not make assumption that every piece of code dealing with JWT has access to the signing key. That would be a disaster. For instance, when user agent makes a request to an identity provider to obtain a new access token (JWT), it may still need to be able to read the header or claims (for example, the expiration time so that it can refresh the token pro-actively rather than re-actively). In this scenario, the user agent has no access to the signing key but it doesn't mean that the user agent shouldn't be able to utilize the content of the token it implicitly trusts.

Another use case is when the server wants to log invalid tokens for later analysis. In other word, when signature validation fails, I want to log the content of the header and claims in readable and structured JSON format rather than in the original compact base64 encoded string. Again, just because a token is invalid doesn't mean that the library shouldn't provide an easy way for application to read the content when it wishes to do so.

Just my .2 cents.

bpappin commented 7 years ago

@jong64 thats how i read it as well. I think the interpretation as executed here may be misunderstood, although I appreciate the work put into it so far.

There are many use-cases where you wouldn't want the client to know the signing token as well.

As I noted in #205 :

Since I'm using this in mobile app clients for client/server authentication, I can never be sure that the client has not been compromised. Sharing the signing key would instantly make it useless. There there is a whole issue with the update cycle getting spread out over years.

vvondra commented 7 years ago

@lhazlewood

Even though I have a use case as well (the key is verified by an API gateway and the upstream up just decodes user data from it, no access to signing key), I really like the patient explanations here and it got me convinced that the library should not support it in the API. A userland implementation like mentioned here https://github.com/jwtk/jjwt/issues/86#issuecomment-180089267 is completely fine for these exceptional cases. I'd support closing this issue.

bpappin commented 7 years ago

I'm actually becoming more, and more convinced that not being able to parse before the key is verified is a disaster.

Not only is that specific case not the spec (as per @jong64 comment above), but it encourages people to include the key where it should never be included, and it encourages people to hack around the library, possibly compromising it in unexpected ways.

Essentially it removed the value proposition when you artificially enforce constraints that should not be enforced.

smileatom commented 7 years ago

@vvondra noone in their right mind would strip the key from the JWT after validation by the API gateway. Thats not the issue described here at all. If the connection between the api gateway and downstream services is however secure, the JWT need not be passed at all. But thats merely an option.

These are not edge cases, a proper API gateway will have multiple keystores and pre-validate JWTs in the process of proxying to services and there is no need to pass the JWT necessarily to services themselves but if it does it should pass the whole JWT and delegate the validation.

As @bpappin mentions its (afaik) not the spec. My point has been and always will be this is a fundamental misunderstanding of what it means to validate a signature. Parsing and signature trust have zero to do with each other. Its obvious on the face of it.

The case of federated microservices is not an edge case, its the only future and its been here for a while.

Nobody should be waiting on this poorly factored library to change. This is not rocket science. You can mod it yourself with little effort and achieve what you want.