0

I'm implementing a JWT-based REST API for the first time and I'm trying to figure out what information to send back on the response entity when a user logs in. The strategies I am considering are:

  • Just an access token, encoded with claims such as user first/last name and other items; or
  • Access token and user first/last name (and other items)

So the response entity for the first strategy might look like:

{
  "accessToken": <JWT-encoded-with-user-first-and-last-name-claims>
}

And the the response entity for the second strategy might look like:

{
  "accessToken": <JWT>,
  "userFirstName": "Jerry",
  "userLastName": "Jingleheimer"
}

Ideally I'd like to go with the first option (its more succinct) but I need to verify my understanding of how JWTs work and are meant to be used.

My understanding is that JWTs are meant to be fully decodeable, meaning I can be given any old JWT, plop it into jwt.io, and see the claims and information, expiry, etc. contained in the JWT. But its read-only, and the moment it expires the server should refuse to use it for authentication. But since its meant to be decoded, its totally fine to put things like user first/last name, their roles, etc. in as token claims, and then use that information on the client-side.

My understanding is that to create a JWT, you need to sign it with a secret, and that when you present the server with a JWT for authentication, it somehow compares the secret that was used to sign the JWT with the secret the server is configured to use for creating the JWTs in the first place. I am not understanding how it does this comparison though, as I would think that if the JWT is decodeable, wouldn't that expose the secret used to sign it?

Is my understanding of JWTs correct, especially with respect to the signing + verification of JWT using secrets (and decoding them)? And are there any anti-patterns with respect to what types of info should/should not be added to a JWT claim? For instance, is it perfectly OK to put user first name and last name in the JWT claims? Otherwise I'd have to do 2 API calls: one to login and get the access token, and another to use the access token to fetch user info. Thanks in advance for any-and-all clarification!

2 Answers 2

1

I am not understanding how it does this comparison though, as I would think that if the JWT is decodeable, wouldn't that expose the secret used to sign it?

The secret isn't in the JWT; it's stored securely on the server that issued the JWT. The JWT only contains a signature that was derived using the contents of the JWT and the secret key. When the server receives a JWT it can calculate a new signature using the contents of the JWT it received and its secret key. If the signature it calculates doesn't match the signature included with the JWT then the JWT is invalid.

For example, say I want to sign the string "abc". I calculate the signature S("abc", key) -> "123". I send you the full token "abc.123".

Now you want to make an authenticated request back to my server. You send me the token "abc.123". I once again calculate S("abc", key) -> "123". The signatures match, and so I know the token hasn't been tampered with.

If you tried to modify the token and sent "xyz.123" I would calculate S("xyz", key) -> "456". "456" does not match the signature "123" from the token and so I know the token has been modified. Since you do not have my key, you cannot calculate the correct S("xyz", key) and so you cannot send me a modified token with the correct signature.


Note that JWTs can also be signed using asymmetric keys where a private key is used for signing and the corresponding public key is used for verification. The concept is very similar: only the server issuing the token knows the private key and therefore no one else can generate a valid signature. The corresponding public key gets published somewhere, which allows anyone to verify that the signature is valid even if they could not generate such a signature themselves.


For instance, is it perfectly OK to put user first name and last name in the JWT claims? Otherwise I'd have to do 2 API calls: one to login and get the access token, and another to use the access token to fetch user info.

Yes, this is perfectly normal. For instance, that's the entire point of OpenID Connect's identity token.

0

Your understanding is broadly correct, and Miles Budnek's answer (+1) provides a good explanation of how the signing/verification process works (if you want more details, the terms to look up are "HMAC"/"Hash-based Message Authentication Code" and "Asymmetric Digital Signature", though note that there is a LOT of incomplete or bad explanations, or outright misinformation, out there about the latter). But the most common use case of JWTs is indeed with HMACs, where S in Miles' answer is an HMAC function, and the first parameter ("abc") is the entire JWT aside from the signature (that is, it's the JWT header and the body/claims/payload), and key is a high-entropy secret known to the server but not to anybody else. The output of this function - the authentication code - is appended to the JWT header and claims/payload/body to form the complete JWT.


To more concretely answer your question:

JWTs are typically readable by the client, but this is not required in most cases, and sometimes they are not. Parsing a JWT isn't hard - it is, as the name implies, JSON, which client-side script can usually easily decode and parse - but sometimes a JWT is encrypted if the server wants to include within it data that is not visible to the user. In theory you don't have to encrypt the whole JWT, just the secrets (as their own values in the body), but that is slightly more complicated and potentially leaks information such as their lengths to a greater extent than encrypting the whole JWT. Obviously, if the whole JWT is encrypted, the client can't parse it at all (there's no point in encrypting the JWT and then letting the client have the key). In such a case, the JWT is an opaque blob to the client... which, honestly, is actually the most common scenario even without encryption (if the JWT contains no client-useful data).

Such secret-from-the-user data is rarely necessary to include in a JWT, and if you think you need it you should consider why very carefully because often that means some other part of your site is insecure or your threat model is bad. Nonetheless, sometimes people choose to encrypt JWTs (or to use libraries which do so by default). If they do, but want to include some public information for the client anyhow, one way to do that is return the public data in parallel with the JWT.

It's worth noting that in many cases, the server doesn't visibly return the JWT to the client at all, but instead returns it in a cookie, which is flagged with the HttpOnly flag. Such cookies are not readable by the client (the user can extract them from the browser via the dev tools or other means, but scripts and other page elements can't read them). Such cookie-based JWTs are inherently opaque blobs to the client whether or not they are encrypted, so if you want the client to receive any data at login, you have to include that data in the response body. However, you don't have to include the JWT in the response body in such cases, as the browser will send it automatically and the script doesn't need it. Obviously, if you use a cookie to store the JWT (or any other access/session token), you need to ensure protection against CSRF attacks.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .