Marc Freeman
Marc Freeman

Reputation: 753

Spring/Boot - JWT not being picked up on GET request

I have a Spring/Boot REST API that serves up JWT tokens for sessions.

I have a session end point with 2 methods, one that authenticates the user, one that validates the Auth Token. I can successfully create and validate tokens on an endpoint (these have no filters on them). As soon as I try and hit the endpoints with a client side application, the requests blow up, I can however, use them okay with a REST client like insomnia.

The code that runs over the JWT token is here:

@Component
public class JwtRequestFilter extends OncePerRequestFilter {


    private final UserService userService;
    private final JwtTokenUtility jwtTokenUtility;

    @Autowired
    public JwtRequestFilter(
            UserService userService,
            JwtTokenUtility jwtTokenUtility
    ) {
        this.userService = userService;
        this.jwtTokenUtility = jwtTokenUtility;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String requestTokenHeader = request.getHeader("Authorization"); <-- null

        String username = null;
        String jwtToken = null;
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtTokenUtility.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                System.out.println("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                System.out.println("JWT Token has expired");
            }
        } else {
            logger.warn("JWT Token does not begin with Bearer String"); <-- Therefore, this is called
        }

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

            UserDetails userDetails = this.userService.loadUserByUsername(username);

            if (jwtTokenUtility.validateToken(jwtToken, userDetails)) {

                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

The problem is that the code reads the Authorization request header as null, but when I check request headers, it's clearly there:

Provisional headers are shown
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJkdWRlY29yMyIsImV4cCI6MTU5NzkzNTQ0NywiaWF0IjoxNTk3OTE3NDQ3fQ.phMnbmkvOW6HKLCnWso-GaX844KMkukS5eADMBko56EEJA-VGgG1qpavwPwHT-tKjxbMuO9VZw3T0rESxQpIqQ
Referer: http://localhost:4200/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36

My authentication set up code is as follows:


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final UserService userDetailsService;
    private final JwtRequestFilter jwtRequestFilter;

    @Autowired
    public WebSecurityConfig(
            JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
            UserService userDetailsService,
            JwtRequestFilter jwtRequestFilter
    ) {
        super();
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.userDetailsService = userDetailsService;
        this.jwtRequestFilter = jwtRequestFilter;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf()
            .disable()
            .authorizeRequests()
            .antMatchers("/auth", "/auth/validate")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        httpSecurity.addFilterBefore(this.jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

The client application that fails is an Angular application, the returned JWT token from auth is stored as a cookie, and interceptor then checks for the cookie, and attaches the value (JWT token) to request headers via an interceptor. This method has worked for me forever (will supply the code if necessary). The screen shot below shows the exact same call returning successfully:

enter image description here

I'm feeling that it may be OPTIONS requests are not allowed on protected endpoints, but I have no evidence that this is the case. Is there an annotation or a utility I can use that will allow me to accept OPTIONS requests? Is there anything here that I have done blatantly wrong? I'm at a loss

Upvotes: 0

Views: 1289

Answers (1)

Marc Freeman
Marc Freeman

Reputation: 753

Fixed - I just updated my HTTP config to this:


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.headers().frameOptions().disable().and().cors().and().csrf().disable();
        httpSecurity.addFilterBefore(this.jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

And I can now accept OPTIONS requests server side.

Upvotes: 0

Related Questions