'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted

I am building a server side REST service application. I have a problem with the JWT authentication token. I can get the token easily after sign in (Here I use Postman).

enter image description here

But when I am trying to authenticate a request to access a protected REST controller using the same token, I get the following error:

io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:354)
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)
    at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
    at com.configuration.jwt.JwtTokenUtil.extractClaims(JwtTokenUtil.java:104)
    at com.configuration.jwt.JwtTokenUtil.getUsernameFromToken(JwtTokenUtil.java:39)
    at com.configuration.jwt.JwtAuthenticationFilter.doFilterInternal(JwtAuthenticationFilter.java:44)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
...

it's like the application doesn't remember the token it generated. Here is the get request from Postman that generated this error:

enter image description here

I guess the source of the exception is from the method extractClaims of my class JwtTokenUtil:

@Component
public final class JwtTokenUtil {

    public static final int EXPIRATION_IN_SECONDS = 120;

    private static final String JWT_SECRET = "Some$ecretKey";

    private Clock clock = DefaultClock.INSTANCE;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    private JwtTokenUtil() {
        // Hide default constructor
    }

    public String getUsernameFromToken(String token) {
        return extractClaims(token).getSubject();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        UserDetailsImp user = (UserDetailsImp) userDetails;
        final String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }

    public Date getIssuedAtDateFromToken(String token) {
        return extractClaims(token).getIssuedAt();
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<String, Object>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(createdDate)
                .setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration * 1000);
    }

    public static String createToken(String username, Date issueDate) {
        String jwtToken = Jwts.builder().setSubject(username).setIssuedAt(issueDate)
                .setExpiration(new Date(issueDate.getTime() + EXPIRATION_IN_SECONDS))
                .signWith(SignatureAlgorithm.HS512, JWT_SECRET).compact();

        return jwtToken;
    }

    public static String getSubject(String token) {
        Claims claims = extractClaims(token);
        return claims.getSubject();
    }

    public static String refreshToken(String token, long expirationInSeconds) {
        final Claims claims = extractClaims(token);

        Date now = new Date();
        claims.setIssuedAt(now);
        claims.setExpiration(new Date(now.getTime() + EXPIRATION_IN_SECONDS));

        return createTokenFromClaims(claims);
    }

    public static boolean isTokenExpired(String token) {
        final Claims claims = extractClaims(token);
        Date now = new Date();

        return now.after(claims.getExpiration());
    }

    private static String createTokenFromClaims(Claims claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, JWT_SECRET).compact();
    }

    private static Claims extractClaims(String token) {
        return Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody();
    }

}

This is my JwtAuthenticationFilter class:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        String header = req.getHeader("Authorization");
        String username = null;
        String authToken = null;

        if (header != null && header.startsWith("Bearer ")) {

            authToken = header.replace("Bearer ", "");

            try {

                username = jwtTokenUtil.getUsernameFromToken(authToken);

            } catch (IllegalArgumentException e) {

                logger.error("an error occured during getting username from token", e);

            } catch (ExpiredJwtException e) {

                logger.warn("the token is expired and not valid anymore", e);
            }
        } else {
            logger.warn("couldn't find bearer string, will ignore the header");
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            if (jwtTokenUtil.validateToken(authToken, userDetails)) {

                String role = "";

                role = userDetails.getAuthorities().size() > 1 ? "ROLE_ADMIN" : "ROLE_TOURIST";

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, Arrays.asList(new SimpleGrantedAuthority(role)));

                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));

                logger.info("authenticated user " + username + ", setting security context");

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(req, res);
    }
}

and I don't know if the sign in controller have anything to do with the issue, but here is the code for it anyway:

@PostMapping(value = "/signin")
    public ResponseEntity<?> signin(@Valid @RequestBody LoginForm loginForm) throws AuthenticationException {

        final Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginForm.getUsername(), loginForm.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        final UserDetails user = userService.loadUserByUsername(loginForm.getUsername());

        final String token = jwtTokenUtil.generateToken(user);

        return ResponseEntity.ok(new JwtResponse(token, user.getUsername(), user.getAuthorities()));
    }

I hope somebody can help.



Solution 1:[1]

Got the same issue, in my case the token passing from angular has quotes in start/end. Resolved by removing them.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let token = localStorage.getItem('token');
    if (token) {
        token = token.replace(/^"(.*)"$/, '$1');
    }

    if (token) {
        request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });
    }

    if (!request.headers.has('Content-Type')) {
        request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
    }

    request = request.clone({ headers: request.headers.set('Accept', 'application/json') });
    console.log("............");
    return next.handle(request);
}

   

Solution 2:[2]

I know it is old question, but I had the same problem and found a solution that works for me so it might be helpful for someone else.

The hashing algorithm converts the provided secret (String value) into a byte array and then performs encoding. The created token is based on bytes and when you try to use a String value to decode the token, error is thrown. Solution was to use bytes arrays as a secret and provide it to decoding function. You can simply turn a String value into a byte array using:

byte[] secret = stringSecret.getBytes();

and use it for decoding.

In your case it would be a little change in the JwtTokenUtil.class:

private static Claims extractClaims(String token) {
    return Jwts.parser().setSigningKey(JWT_SECRET.getBytes()).parseClaimsJws(token).getBody();
}

Solution 3:[3]

Had similar issue with same error message. I realized it was due to white spacing in the public key provided. You might check it out. Fixed with this code snippet:

   String PUB_KEY = System.getenv("PUBLIC_KEY") ;  // remove ---PUBLIC KEY--- & ---END PUBLIC KEY ---
   String PUBLIC_KEY = "";
        if (!PUB_KEY.isEmpty()) {
            PUBLIC_KEY = PUB_KEY.replace(" ", "");
        }

Hope it helps.

Solution 4:[4]

I had this error while trying to parse my JWT BUT my mistake was using two different instances of my TokenUtils.class, and they had different keys because I delegated the Key creating to the lib.

SOLUTION: Created a @Component anotated TokenUtilsBean class with a @Bean annotated method returning a instance of TokenUtils.class. So I used dependency injection to get the same instance on my AuthFilter.class and CustomUserService.class.

@Component
public class TokenUtilsBean {

  @Bean
  public TokenUtils tokenUtils() {
    return new TokenUtils();
  }
}

Here's my class and the JUnit tests, maybe someone is doing this wrongly like me.


public class TokenUtils {
  private final KeyPair keys = Keys.keyPairFor(SignatureAlgorithm.RS512);
  private final ObjectMapper mapper = new ObjectMapper();

  public String generateToken(CustomUser user) {
    Map<String, Object> claims = mapper.convertValue(user, Map.class);

    return Jwts.builder()
            .setSubject(user.getUsername())
            .setClaims(claims)
            .signWith(keys.getPrivate())
            .compact();
  }

  public Boolean validateToken(String jwt) {
    try {
      Jwts.parserBuilder()
              .setSigningKey(keys.getPublic())
              .build()
              .parseClaimsJws(jwt);

      return true;
    } catch (Exception e) {
      return false;
    }
  }
}


Here's the JUnit Unit tests

  @Test
  public void validateTokenWithDifferentInstanceShouldBeFalse() {
    String jwt = tokenUtils.generateToken(user);

    //Validating token with a different instance
    Assertions.assertFalse(secondTokenUtils.validateToken(jwt));
  }

Solution 5:[5]

You have a few options.

Cookies, Session Storage and Local Storage.

Cookies:

The Document property cookie lets you read and write cookies associated with the document. It serves as a getter and setter for the actual values of the cookies.

Session Storage:

The read-only sessionStorage property accesses a session Storage object for the current origin. sessionStorage is similar to localStorage; the difference is that while data in localStorage doesn't expire, data in sessionStorage is cleared when the page session ends.

Local Storage:

The localStorage read-only property of the window interface allows you to access a Storage object for the Document's origin; the stored data is saved across browser sessions.

localStorage is similar to sessionStorage, except that while localStorage data has no expiration time, sessionStorage data gets cleared when the page session ends — that is, when the page is closed. (localStorage data for a document loaded in a "private browsing" or "incognito" session is cleared when the last "private" tab is closed.)

You could also use PHP Sessions, Save and pull from a database and various other session or storage handling. But they would most likely be total over kill if you don't want to store the rating for anything other than just a single user refreshing a page. Comment me if you intend to actually have Ratings saved by users for viewing tih everyone and I'll update the answer.

But you do want to be careful of XSS with something like this.

Update:

Ok so to store the rating for the entire site for any viewer you would want to use Database Storage.

You will have to use a few things here depending on your setup.

First you want to use javascript or form post functionality to send the value to the server side. Lets say PHP which is most likely. you would then have a PHP script store the value onto a database. You have the option of SQL or NoSQL.

JavaScript XMLHttpRequest:

To send an HTTP request, create an XMLHttpRequest object, open a URL, and send the request. After the transaction completes, the object will contain useful information such as the response body and the HTTP status of the result.

function reqListener () {
  console.log(this.responseText);
}

    var oReq = new XMLHttpRequest();
    oReq.addEventListener("load", reqListener);
    oReq.open("GET", "http://www.example.org/example.txt");
    oReq.send();

Sending Via a Form:

At its most basic, the web uses a client/server architecture that can be summarized as follows: a client (usually a web browser) sends a request to a server (most of the time a web server like Apache, Nginx, IIS, Tomcat, etc.), using the HTTP protocol. The server answers the request using the same protocol.

PHP to Database via MYSQLI:

The mysqli extension allows you to access the functionality provided by MySQL 4.1 and above.

PHP to Database via PDO:

The PHP Data Objects (PDO) extension defines a lightweight, consistent interface for accessing databases in PHP.

MariaDB (MySQL):

The easiest way to understand a database is as a collection of related files. Imagine a file (either paper or digital) of sales orders in a shop. Then there's another file of products, containing stock records. To fulfil an order, you'd need to look up the product in the order file and then look up and adjust the stock levels for that particular product in the product file. A database and the software that controls the database, called a database management system (DBMS), helps with this kind of task

NoSQL :

When people use the term “NoSQL database,” they typically use it to refer to any non-relational database. Some say the term “NoSQL” stands for “non SQL” while others say it stands for “not only SQL.” Either way, most agree that NoSQL databases are databases that store data in a format other than relational tables.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 John Erbynn
Solution 4 Luis Felipe Martins
Solution 5