'Illegal base64url character: ' ' when getting claims/decode from token Java JWT Spring Boot
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
Solution 1:[1]
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
Solution 2:[2]
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.
Solution 3:[3]
U can also use this :
jwtToken = requestTokenHeader.substring("Bearer ".length());
It solved the some problem for me.
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 | chrylis -cautiouslyoptimistic- |
Solution 3 | mohamedpieerre |