Search code examples
angularspring-bootspring-securityjwtangular-http-interceptors

Angular Interceptor: headers added in the request not set properly, can't fetch them from the API


I'm trying to add some custom headers in my angular interceptor as follow:

@Injectable()
export class HttpJwtAuthInterceptor implements HttpInterceptor {

  constructor() {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    localStorage.setItem('token', 'qsdfqsdfqsdfqsdf');
    if (localStorage.getItem('cuid') && localStorage.getItem('token')) {
      request = request.clone({
        setHeaders: {
          'sm_universalid':'blablabla',
          Authorization: `Bearer ${localStorage.getItem('token')}`,
        },
      });
    }



    return next.handle(request);
  }
}

The above interceptor was added into providers property in app.module.ts:

  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpJwtAuthInterceptor,
      multi: true,
    },
    { provide: BASE_PATH, useValue: environment.apiUrl },
  ],

In my spring boot application, I am adding a filter to check if the headers are received, if not it throw an exception:

String cuid = request.getHeader(SM_UNIVERSAL_ID);
if (cuid == null || cuid.isEmpty()) {
    log.error("Failed authentication: cuid header not found in the request or it is empty");
    throw new FailedAuthenticationException("cuid header not found in the request or it is empty:"+cuid);
}

Unfortunately the filter can't find the added headers, I tried to log the list of headers received and I found that they were added as value for access-control-request-headers header: enter image description here

How can I get those headers separately? and why they were added into access-control-request-headers header?

Bellow a snapshot of my spring security class config (the header filter is applied there):

 @Configuration
@EnableWebSecurity
@Slf4j
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final HeaderVerificationFilter headerVerificationFilter;
    private final JwtAuthorizationFilter jwtAuthorizationFilter;
    private final JwtAuthEntryPoint jwtAuthenticationEntryPoint;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //csrf is disabled because we are not using cookies
        http.csrf().disable();

        // No session will be created or used by spring security
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //Entry points
        http.formLogin().loginProcessingUrl("/api/login").permitAll()
                //Disallow everything else
                .and().authorizeRequests().anyRequest().authenticated();

        //if a user try to access a resource without having enough permissions
        http.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);

        // Apply Header and JWT filters
        http.apply(new JwtTokenFilterConfigurer(headerVerificationFilter, jwtAuthorizationFilter, jwtAuthenticationEntryPoint));
    }


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

    @Override
    public void configure(WebSecurity web) throws Exception {
        // Allow swagger to be accessed without authentication
        web.ignoring().antMatchers("/v2/api-docs")//
                .antMatchers("/swagger-resources/**")//
                .antMatchers("/swagger-ui.html")//
                .antMatchers("/configuration/**")//
                .antMatchers("/webjars/**")//
                .antMatchers("/public");
    }

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

Solution

  • I don't know why, but I had to add my own custom CORS Policy config in my spring security config class, like that:

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // allow requests
        config.setAllowedOriginPatterns(List.of("*"));
    
        //allowed methods
        config.setAllowedMethods(Arrays.asList("POST", "OPTIONS", "GET", "DELETE", "PUT", "PATCH"));
    
        // headers allow in request
        config.setAllowedHeaders(
                Arrays.asList("X-Requested-With", "Origin", "Content-Type", "Accept", "Authorization"));
    
        // headers exposed in response
        config.setExposedHeaders(Arrays.asList("Token", "Authorization", "Content-Type", "Content-Disposition"));
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
    

    And also, I had to permitt all method of type Options, like that:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //csrf is disabled because we are not using cookies
        http.csrf().disable();
    
        // No session will be created or used by spring security
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
        //Entry points
        http.formLogin().loginProcessingUrl("/api/login").permitAll()
                //Disallow everything else
                .and().authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()//<-- this one
                .anyRequest().authenticated();
    
        //if a user try to access a resource without having enough permissions
        http.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
    
        // Apply Header and JWT filters
        http.apply(new JwtTokenFilterConfigurer(headerVerificationFilter, jwtAuthorizationFilter, jwtAuthenticationEntryPoint));
    }