5

When I get some claims from a JWT Token to validate user authentication I get the following error:

Illegal base64url character: ' '

Creating a JWT goes completely fine but "decoding" seems to have some issues... I also tried a base64url decoder to decode the token before getting the claims but then the token is unvalid.

My JWToken class where I encode and "decode" the token:

@Component
public class JWToken {

    private static final String JWT_USERNAME_CLAIM = "sub";
    private static final String JWT_ADMIN_CLAIM = "admin";

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

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

    @Value("${jwt.duration-of-validity}")
    private int expiration;


   public String encode(String name, boolean admin) {
       String token = Jwts.builder()
               .claim(JWT_USERNAME_CLAIM, name)
               .claim(JWT_ADMIN_CLAIM, admin)
               .setSubject(name)
               .setIssuer(issuer)
               .setIssuedAt(new Date(System.currentTimeMillis()))
               .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
               .signWith(SignatureAlgorithm.HS512, passPhrase).compact();

       return token;
   }


    //for retrieving any information from token we will need the secret key
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser().setSigningKey(passPhrase).parseClaimsJws(token).getBody();
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    //retrieve username from jwt token
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    //retrieve expiration date from jwt token
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    //check if the token has expired
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    //validate token
    public Boolean validateToken(String token, String name) {
        final String username = getUsernameFromToken(token);
        return (username.equals(name) && !isTokenExpired(token));
    }
    


}

My request filter:

@Component
public class JWTRequestFilter extends OncePerRequestFilter {
    private static final Set<String> SECURED_PATHS =
            Set.of("/api/offers", "/api/bids");

    private final JWToken jwToken;

    @Autowired
    public JWTRequestFilter(JWToken jwToken) {
        this.jwToken = jwToken;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {



        final String requestTokenHeader = request.getHeader("Authorization");
        String username = null;
        String jwtToken = null;

        String servletPath = request.getServletPath();

        if (HttpMethod.OPTIONS.matches(request.getMethod()) || SECURED_PATHS.stream().noneMatch(servletPath::startsWith)) {

            filterChain.doFilter(request, response);
            return;
        }

        // JWT Token is in the form "Bearer token". Remove Bearer word and get
        // only the Token
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                System.out.println(jwtToken);
                username = jwToken.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                System.out.println("Unable to get JWT Token");
                throw new AuthenticationException("authentication problem");
            } catch (ExpiredJwtException e) {
                throw new AuthenticationException("authentication problem");
            }
        } else {
            System.out.println(requestTokenHeader);
            logger.warn("JWT Token does not begin with Bearer String");
            //throw new AuthenticationException("authentication problem");
        }

        if(jwToken.validateToken(jwtToken, username)){
            filterChain.doFilter(request, response);
        }

        // Once we get the token validate it.

        }

}

The error in my console when I do a get request for /api/offers with the generated JWT token in the header:

io.jsonwebtoken.io.DecodingException: Illegal base64url character: ' '
    at io.jsonwebtoken.io.Base64.ctoi(Base64.java:221) ~[jjwt-api-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.io.Base64.decodeFast(Base64.java:270) ~[jjwt-api-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:23) ~[jjwt-api-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.io.ExceptionPropagatingDecoder.decode(ExceptionPropagatingDecoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:309) ~[jjwt-impl-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:550) ~[jjwt-impl-0.11.2.jar:0.11.2]
    at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:610) ~[jjwt-impl-0.11.2.jar:0.11.2]
    at team10.server.aucserver.security.JWToken.getAllClaimsFromToken(JWToken.java:47) ~[classes/:na]
    at team10.server.aucserver.security.JWToken.getClaimFromToken(JWToken.java:51) ~[classes/:na]
    at team10.server.aucserver.security.JWToken.getUsernameFromToken(JWToken.java:57) ~[classes/:na]
    at team10.server.aucserver.security.JWTRequestFilter.doFilterInternal(JWTRequestFilter.java:52) ~[classes/:na]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.1.jar:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.1.jar:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.1.jar:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]

And line 47 is the getAllClaimsFromToken method in the JWToken class.

For an extra example this is one of the tokens the encode genrated:

Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyb25ueSIsImFkbWluIjpmYWxzZSwiaXNzIjoicHJpdmF0ZSBjb21wYW55IiwiaWF0IjoxNjExMDA1NTc4LCJleHAiOjE2MTEwMDY3Nzh9.dQwEVfSNa6EIx-U-bgHN50hmrN0wYkj-8jXRoFLTx6JB53ERBWuGUeiXLqtiJ_jTGxEISB-Lv7E9KAyPk8nV3g
Ronny Giezen
  • 557
  • 2
  • 10
  • 21
  • Put error text in your post as text. Not as image. Having said that: I don't see your code catching that error anywhere, so which line of _your_ code triggers this errors? – Mike 'Pomax' Kamermans Jan 18 '21 at 21:48
  • @Mike'Pomax'Kamermans I edited the post and put the error as text. When I do a request that goes through the filter with an JWToken in the header I get this error. – Ronny Giezen Jan 18 '21 at 21:56
  • _Why_ are you rewriting your own filter instead of using the out-of-the-box Spring Security JWT support? – chrylis -cautiouslyoptimistic- Jan 18 '21 at 22:20
  • Did you end up figuring it out by any chance? I am in a similar situation at the moment. – A.J Jun 07 '21 at 21:06
  • 1
    @A.J yes for some reason the substring function still kept on some white space before the token so I changed that to: requestTokenHeader.split(" ")[1].trim() – Ronny Giezen Jun 08 '21 at 11:47
  • 1
    @A.J I also answered my question below just now – Ronny Giezen Jun 08 '21 at 11:51
  • @RonnyGiezen hmm I added your change but it didn't fix the issue. I do have a slightly different error - `Illegal base64 character: '>'`, although I'd guess it is because of the same problem. – A.J Jun 08 '21 at 19:55
  • @A.J Do you see that character anywhere when you print the token in the console after trimming it? – Ronny Giezen Jun 08 '21 at 20:16
  • @RonnyGiezen This happens just in general when I try to generate the token as well. It fails in your `public String encode(String name, boolean admin) {` method. But yea, if I send a token and output it then I don't see that `<` symbol. – A.J Jun 08 '21 at 20:25
  • @RonnyGiezen FYI this happened only after moving from version `0.9.1` to `0.11.2`. – A.J Jun 08 '21 at 20:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/233524/discussion-between-ronny-giezen-and-a-j). – Ronny Giezen Jun 08 '21 at 20:48

4 Answers4

7

What you are decoding isn't the token, you're trying to decode the entire header value. Bearer isn't part of the token, it's the authentication scheme.

More generally, you're writing your own security infrastructure, which is almost always a very bad idea. Spring Security JWT handles all of this for you automatically; use it instead.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • Hi thanks for your comment, I have followed some tutorial from baeldung. I thought this line" jwtToken = requestTokenHeader.substring(7); " removed the Bearer part. I will look into on how I can make it that Spring security automatically handles it – Ronny Giezen Jan 19 '21 at 07:48
  • 1
    @RonnyGiezen It should, but apparently you're still getting the space as part of it. At a minimum, you should debug your program and inspect the value of the `jwtToken` variable. – chrylis -cautiouslyoptimistic- Jan 19 '21 at 08:15
3

For some reason the substring function kept some white space before the token. I changed that line in my JWTRequestFilter.

Old:

jwtToken = requestTokenHeader.substring(7);

New:

jwtToken = requestTokenHeader.split(" ")[1].trim();

The added .trim() will delete any white space before or after the string, so that was the solution for me

Ronny Giezen
  • 557
  • 2
  • 10
  • 21
0

U can also use this :

jwtToken = requestTokenHeader.substring("Bearer ".length());

It solved the some problem for me.

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 05 '22 at 12:18
0

For me, the problem has been solved after using a proper Base64 compatible secret (i.e., the secret should not have any illegal characters). If you still run into the issue, then double check your secret key i.e., are you using the same secret while decoding the token?

You can use the below code to securely generate secret key:

public String createSecretKey() {
    SecureRandom secureRandom = new SecureRandom();
    byte[] secretBytes = new byte[36]; //36*8=288 (>256 bits required for HS256)
    secureRandom.nextBytes(secretBytes);
    Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
    return encoder.encodeToString(secretBytes);
}
Vasu
  • 21,832
  • 11
  • 51
  • 67