JWT Verify client-side?
Asked Answered
D

8

36

I have a nodejs api with an angular frontend. The API is successfully using JWT with passport to secure it's endpoints.

I am now conscious that after the tokens have expired, my front end will still allow the user to request my api endpoints without prompting them to reenter their log in details to get a fresh token.

This is how my backend generates the token:

function generateToken(user) {
  return jwt.sign(user, secret, {
    expiresIn: 10080 // in seconds
  });
}

So to implement this logic I think I need to verify the JWT token client-side. Q1, is this a sensible approach.

Q2, the JWT library I am using seems to require a public key to use it's verify() function. I don't seem to have a public key, only a secret, which I just made up, so it wasn't generated with a pair. Where does my public key come from, or is there another way of verifying my token without this?

This all seems like it should be obvious and that I have missed something, so apologies if this is a stupid question, but I can't seem to find the answer?

Demoralize answered 4/5, 2017 at 16:18 Comment(0)
O
70

TL;DR

  1. You must verify the signature of JWS in the server always.
  2. Client-side signature verification doesn't gives much, unless you have a specific case where it makes sense don't do it.
  3. You don't need to verify the signature of a JWS token to check expiration in the client. (unless you were encrypting the claims, aka using JWE, in that case you need to do something similar because you need a key to decrypt the claims).
  4. You don't need to verify the signature of a JWS to check expiration in the server neither, but you should because this gives you the certainty that nobody has altered the expiration (otherwise the verification will fail because if the claims change then the recalculated signature will differ)
  5. To read non encrypted claims you just only need to decode them. You could use jwt-decode in the client.

I am now conscious that after the tokens have expired, my front end will still allow the user to request my api endpoints [...]

So to implement this logic I think I need to verify the JWT token client-side

If I understood you correctly you are talking about checking if a JWS has expired in the client side. In order to do this you don't need to verify the token signature (although the library you are using seems to be doing both things at the same time for you, but also lets you to disable expiration control with ignoreExpiration flag). (Unless you're encrypting the claims, aka using JWE) The RFC 7515 (JWS) says nothing about expiration. Message Signature or MAC Validation doesn't control expiration (and it shouldn't because signatures gives you authenticity and integrity). Even the RFC 7519 (JWT) doesn't control the expiration claim for resolve if a JWT is valid or not.

Also, all the claims are optional.

So, you could check if a JWT has expired or not without verifying the signature, hence you don't need neither a public key (for asymmetric encryption like RSA) or a secret key (for symmetric encryption like AES). In JWT and JWS tokens, the claims are just plaintext base64 encoded so you could just decode the payload without verifying if the signature is valid and read the expiration claim. If you are encrypting the payload (aka using JWE) then you will not be able to do this.

A note from jjwt library

JWTs can be cryptographically signed (making it a JWS) or encrypted (making it a JWE).

Here is a ligthweigth library from auth0 to decode the base64encoded claims of a JWT/JWS token. A guy is even asking about checking expiration.

I don't know why you think that you should be doing this control client-side, the only advantage is avoiding sending API request that the client knows that will fail. And they should fail because the server should be validating that the token hasn't expired, previous signature verification (with secret/private key) obviously.

The RFC 7519 says about this claim:

The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.

In a web app like the one you say the use of tokens is to allow stateless servers to authenticate client requests. The goal of the OPTIONAL expiration claim is to allow the server have some control over the generated JWS (if we are using JWT for authentication signing them is a must so we should be talking about JWS).

Without expiration, the tokens will be valid forever or until the key used to signing them change (this will make the verification process to fail). By the way, invalidating sessions is one of the most notorious disadvantages of using stateless authentication.

Session invalidation becomes a real problem if we are including information in the JWS payload (aka claims) used for authorization, for example which roles the user have.

From Stop using JWT for sessions

but more seriously, it can also mean somebody has a token with a role of admin, even though you've just revoked their admin role. Because you can't invalidate tokens either, there's no way for you to remove their administrator access

The expiration control doesn't solve this problem and I think is more oriented to avoid session hijacking or CSRF attacks.

An attacker using CSRF will be able to make a request with an expired JWS to your API skipping the expiration control.

A different issue is verifying the signature in the client using the public or secret key.

Regarding your question

I am using seems to require a public key to use it's verify() function. I don't seem to have a public key, only a secret, which I just made up, so it wasn't generated with a pair.

The verify method you pointed out says explicitlly that it accepts a public or secret key.

jwt.verify(token, secretOrPublicKey, [options, callback])

secretOrPublicKey is a string or buffer containing either the secret for HMAC algorithms, or the PEM encoded public key for RSA and ECDSA

I assume you are using neither and you are using a string like 'shhhh'.

var token = jwt.sign({ data: '¿Donde esta Santiago?'}, 'shhhh');

Then you should do

var decoded = jwt.verify(token, 'shhhhh');

However, the question here is: Is client-side signature verification really needed?

I think is not, at least not for this kind of application where the client just uses the JWS to send subsequent request to the server saying: "Hey server, I'm Gabriel and I have a paper (token) here that assures that and that paper is signed by you." So if the client doesn't validate the JWS and a MITM had successfully gave to that client a JWS signed by himself (instead to the JWS signed by the server), then the subsequent request will simply fail. Like expiration control, signature verification only prevent the client to make request that will fail.

Now, client side verification requires sending the public or secret key. Sending public key doesn't represent a security concern but it's extra effort and processing with little benefits.

Sending secret keys (like 'shhhh') can represent a security issue because is the same key that is used to sign tokens.

Obediah answered 9/9, 2017 at 17:14 Comment(5)
From docs: Returns the payload decoded if the signature is valid and optional expiration, audience, or issuer are valid. If not, it will throw the error. You can put anything you want on the payload.Obediah
you do need to verify jwt tokens on the client when using async RS256, which is the recommended way. The reason is because the server itself could not be your server - a reverse proxy - or 'server pretending to be your server' could issue tokens and guess what? at that point your client could be sending payloads with information to the pretend server. That is not good. So what do you do? You verify the jwt on the client before sending it to the server. so you're right about the server should return 401 or something when it receives an invali tokenStilu
Hi, when using an asymmetrical algorithm it seems to me that it's perfectly ok to do some verification client-side, or at least server-side but without calling the auth server (example: a Next.js middleware that acts as a proxy, you may want to check the token there). In this case there is a public key, no reason to make it a secret. However, I struggle to find reliable information, Stack Overflow answers are contradicting each other and not bringing enough sources.Fate
Who does the audience value check? Browser or the server?Theatricalize
It might be okay to do client-side verification using RSA, but the point is that there isn't much (any) value in it. From the UI's perspective, it doesn't matter if the user messes around with the token. Your application's value isn't clicking buttons. It's all of the business logic and data hosted on the backend. Tricking the UI into showing a button that says they can do something they can't does not matter (which they could do regardless of signature verification). It matters that they can't actually submit an order or look up user information.Arteritis
F
4

I'll just put it here for those who got here looking for jwt verification on the browser with a public key.

Library: https://kjur.github.io/jsrsasign/

Example: https://kjur.github.io/jsrsasign/tool/tool_jwtveri.html

Code example: https://github.com/kjur/jsrsasign/blob/master/tool/tool_jwtveri.html

API: https://kjur.github.io/jsrsasign/api/symbols/KJUR.jws.JWS.html#.verifyJWT

P.S. Don't use your secret key for browser jwt verification! Public key only!

Fog answered 18/4, 2021 at 7:59 Comment(0)
D
2

I think verifying JWT token at client-side is not a good idea.
IMO;

  1. Whenever a user logs in, generate access and refresh token and return to user something like this;

    { "accessToken": <<accessToken>> "refreshToken": <<refreshToken>> "expiresAt": <<expiresAt>> }

    So client can understand when access token expire and can refresh it with refresh token.

  2. Encrypt the data that you put in the access token because there is a chance to access the data without secret key. But of course someone needs to secret key to verify.

Dees answered 4/5, 2017 at 18:24 Comment(0)
I
1

Q1: Token verification on client is a bad idea. What you can do is to save a token together with a same expired date on client and then refresh/remove a token. But my thought that it is better to have some date checkig on server side cause exist simple rule: Don't trust the client cause it can always send malicious code.

Q2: JWT don't need any public key. It always must have private key storing on server side cause if someone known your secret key your token don't make any sense. You only can add some payload to do it more complex.

Irreplaceable answered 4/5, 2017 at 18:26 Comment(0)
D
1

Managing when the token expires on the client side so you can avoid sending tokens which you know will be rejected is purely an optimisation to avoid an extra roundtrip with the server. Its a perfectly valid concern but it's not a security concern. Its up to you to decide if you need that level of optimisation. The server must validate the token signature and reject expired tokens as a security concern. Tokens don't need to be encrypted unless they contain sensitive data that you don't wish to be visible to an end user or an attacker that obtains a copy of the token somehow. Tokens should be transmitted over HTTPS / SSL as good security practice. Access Tokens are usually short lived. If you also use refresh tokens, never store one in a browser unless it's a secure cookie set by the server for the same origin domain and not accessible to browser scripting. In that case Refresh tokens should still be regularly rotated.

Dendy answered 13/8, 2021 at 7:56 Comment(0)
S
0

The people on here saying 'you shouldn't do it at the client' are wrong. You should do it at both if you can. Why? So someone doesn't throw burp up, catch the request, manipulate it, and go on with their life. If you have a CRUD app with zero actual content then it's not a big deal. But if you're whole auth scheme for a SPA is dependent on "if(!token) then authState === notAuthed" kind of logic you're kind of limiting yourself by not doing any client side validation and letting the client do whatever. Session ended and you're not validating at the client you can just change the expiry and keep doing whatever on the page. Again, not a big deal if you have legit 100% of your data being pulled from additional API calls (assuming you're validating everything at the APIs), but you really shouldn't be in the business of assuming everything on the client end is fine and dandy. You don't need to drop crazy amounts of $$ to implement zero trust concepts. Validate it every step of the way on the client if you can. Shouldn't have green checkmarked what was given one for the flat out "you shouldn't do that." Vulnerabilities and threat modeling look at the entire system as a whole. This is an easy whole to plug.

Some of the answers here are coming from a place of not understanding the concepts of PKI. Used node:crypto to generate a key pair and sign the JWT. node crypto comes with node now without having to install any additional packages. node-jsonwebtoken works well for making a JWT. The general steps:

  1. make a key pair.
  2. make a JWT
  3. Sign the JWT with the privatekey. Store it or delete it in a manner that makes sense for your auth pattern and use case.
  4. Issue the JWT to the client
  5. On the client side verify the signature with the public key.

look at gnerateKeySync section https://nodejs.org/api/crypto.html#crypto

npm install jsonwebtoken

const { generateKeyPairSync } = await import('node:crypto'); import jwt from 'jsonwebtoken';

If you use a password/phrase to encrypt the private key you need to pass that in to sign the JWT properly of you won't be able to validate properly with the public key.

const signOptions = { algorithm: 'RS256' } let signedJWT = jwt.sign(JWT, {key: privateKey, passphrase: passKey}, signOptions);

key and passphrase are argument names, privateKey and passKey are where you would put your inputs for that.

Someone could just swap out the entire token and public key (maybe? I'm really not sure if they could with TLS) and make it work potentially, but at that point you're looking at an attack path that's close to a full on man in the middle to intercept an outbound request to get a JWKS and inserting a malicious one.

SPA's as a whole have a potential gap with authentication because there isn't much to trigger server validation aside form API calls by default. You can add hooks to call back to the server, get the public key, validate on the client, and then also pass something back from the client to the server side to validate so you have the warm and fuzzy. That's what I'm building out. Probably over kill, but whatever. It's at a least really academic in thinking through how locked down you can make it. The one shortfall of httpOnly cookie is you can't do anything client side with it, so you have need to have something at the client to validate which if done wrong introduces tons of issues hence why issuing another token signed with another key pair and going from there. That's also in addition to an httponly cookie and crsf token for validating every API call.

Staciastacie answered 11/8, 2023 at 0:43 Comment(1)
The client, by definition, is insecure. They have control over it. With enough time and dedication, they can simply remove the client side checks through DevTools... They can rewrite just enough of the application to appear as though they're an admin regardless of their jwt. The only place you're really secure is on the back-end. As long as they can't make any requests or system changes that aren't allowed, if they're manipulating data (or logic) on the front, what does it matter?...Arteritis
C
0

Loads of answers on why or why not to do it.

In our app we send an extra expire date and check towards that on every pageflip and focus event. Just to make sure de user is not entering loads of details only to find out the endpoint is not excepting it because of the expired token. If the expire date is reached an extra login panel pops in view to check the credentials.

This approach keeps the re-login separate from the token logic. It does't really matter if the token is correct or not, it's just for valid users to not do work on the page in vain. The endpoint will reject any requests by invalid tokens.

btw. This extra expire date is an hour before actual expire of the token.

Cyton answered 24/2 at 10:47 Comment(0)
B
-2

Answer 1: It is not considered to be a good approach to verify your auth token on the client side as it involves secret key while encoding/decoding it and keeping the secret key on the client side is not secure.

Creating Token

jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: 60 * 60 });

Verifying Token

jwt.verify(token, 'secret', function(err, decoded) { console.log(decoded.foo) // bar });

Answer 2: JWT involves secretORPublic key while encoding and decoding token. It has to be declared or kept in the config file somewhere on the server side.

Explanation: Decoding means decoding from Base64, there's no secret key involved in that process. On the other hand, verifying a JWT would require a secret key because it would involve a cryptographic signature operation.

To sum up, decoding does not need the secret (remember decoding is just interpreting base64) and verifying/signing does require it

Bloomsbury answered 4/5, 2017 at 18:17 Comment(1)
Signatures use public keys (RS256 for example)Pelargonium

© 2022 - 2024 — McMap. All rights reserved.