4

I am calling a web api project from a React application. The api uses JWT for authentication. If the application supplies the API with an expired JWT token, it receives a 401 response as expected, but the 'www-authentication' header (which includes the message saying that the reason for the 401 is due to expired token) is missing.

If I run Fiddler during the request and inspect the response, I can see the header is present:

HTTP/1.1 401 Unauthorized
Date: Wed, 22 Apr 2020 01:07:32 GMT
Server: Kestrel
Content-Length: 0
WWW-Authenticate: Bearer error="invalid_token", error_description="The token expired at '04/21/2020 17:33:17'"
Access-Control-Allow-Origin: *

But the response object on the error received by my React application (which is using axios to call the API) does not have the 'www-authenticate' header:

Response as shown in console

Here is the calling code:

async function getAllParts() {

  const gpuUrl = urls.apiGetGpuUrl;
  const moboUrl = urls.apiGetMoboUrl;
  const cpuUrl =urls.apiGetCpuUrl;
  const ramUrl =urls.apiGetRamUrl;

  const requestOptions = {
    method: 'GET',
    headers: authHeader()
  };

  const gpuData = axios.get(gpuUrl, requestOptions);
  const moboData = axios.get(moboUrl, requestOptions);
  const cpuData = axios.get(cpuUrl, requestOptions);
  const ramData = axios.get(ramUrl, requestOptions);
  try{
    const response = await Promise.all([gpuData, moboData, cpuData, ramData]);
    return handleMultipleResponses(response);
  } catch(error){
    console.log(error); // This error contains a response object with no 'www-authenticate' header
  }  
}

As mentioned, I can see from Fiddler that the response does contain the header, but how can I access it from my application? Calling the API from postman produces an expected result with the header present:

Response as shown in Postman

Any help would be greatly appreciated.

EDIT:

Just to add for clarification, in the calling code, the 'authHeader()' function sets the header as follows:

export function authHeader() {
  let user = JSON.parse(localStorage.getItem('user'));
  if (user && user.token) {
      return { 'Authorization': 'Bearer ' + user.token };  //     
  } else {
      return {};
  }
}

I believe this to be set correctly, as the data is successfully retrieved when the JWT has not expired.

GWSD
  • 41
  • 1
  • 3
  • If its oauth server then it should return error response in json format – CoderSam Apr 22 '20 at 18:28
  • 1
    @CoderSam yes, it does return the error response as a json object, as shown in the console response above. The error object has the response object as a property, so I should be able to access the response headers through error.response.headers, but the only header in the response is {content-length: "0"}. The 'www-authenticate' header is missing, along with any other headers. Yet the response clearly shows the headers are present when viewed in Fiddler. Postman also receives the response with headers included. I just can't figure out why they are not available in my application. – GWSD Apr 23 '20 at 09:30
  • Apparently you will not be allowed to access all headers if authorization attribute is not available in auth response. Frontend javascript can have access to content-length, pragma and few more headers. Use Access-Control-Expose-Headers value to include authorization or www-authenticate to be get exposed. If not browser will deny you access. – CoderSam Apr 26 '20 at 07:43
  • @CoderSam yes, you are right - thank you for pointing me in the right direction. I set up Access-Control-Expose-Headers to expose the 'www-authenticate' header, although in my case I had to change it to 'x-www-authenticate' to get it to work. Many thanks. – GWSD Apr 29 '20 at 07:46
  • Great to hear that. Happy coding – CoderSam Apr 30 '20 at 10:09

1 Answers1

0

I was having the same problems with accessing the www-authenticate header with axios on my react app.

I finally got it to work by modifying my backend to expose the www-authenticate header in the cors configuraiton.

My backend is running on spring boot 2.6.4

So I added this bean in my WebSecurityConfigurerAdapter config class:

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
        config.addExposedHeader("WWW-Authenticate");
        config.addExposedHeader("Authorization");
        source.registerCorsConfiguration("/**", config);
        return source;
    }

And then adding the above bean as cors config source in the configure method:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().configurationSource(corsConfigurationSource())
            .and()
            .csrf().disable()
            .and()
            .authorizeRequests(configurer ->
                configurer
                    .antMatchers(
                        "/",
                        "/error"
                    )
                    .permitAll()
                    .anyRequest()
                    .authenticated()
            );
    }

k32y
  • 407
  • 6
  • 11