Angular 4: unable to read headers from response - not a CORS issue
Asked Answered
B

2

5

In the context of a server automatically renewing a token, I'm struggling with the basics: Obtaining header data from a response.

It does not seem to be CORS related as my Node/express allwos the Authorization/x-access-token and reponds accordingly (see network tab screeencap below).

The first step I want to see working is simply reading the header from the response. See my code, it's boilerplate as found in the docs. Event getting "Content-Length" returns null.

auth-service.ts

login(username:string, password:string):Observable<boolean>{
    this.loggedIn = false;
    return new Observable( (observer:Observer<boolean>) => {

      const body = {user: username, pwd: password};
      const url = this.config.API_ENDPOINT_PUBLIC_AUTH;

      this.httpClient.post(url, body, {
        responseType: 'text',
        observe: "response"
      }).first().subscribe(
        (response:HttpResponse<string>) => {

          // DEBUG
          console.log(response.headers.get('Authorization'));
          console.log(response.headers.get('X-Test-Header'));
          console.log(response.headers.get('Content-length'));


          this.token = response.headers.get('Authorization');
          this.storeToken(this.token);
          this.currentUser = username;
          this.loggedIn = true;
          this.authChanged$.next('auth');
          observer.next(true);
        },
        (err) => observer.next(false),
        () => {},
      );
    });
  }

Console output:

null

null

null

Compare this to the contents of my network tab for this request:

enter image description here

Needless to say my HttpInterceptor doesn't work either - upon being provided "Authorization" header in a response, it uses the value as new token. This to implement auto-renewal of tokens:

token.interceptor.ts

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const authService = this.injector.get(AuthService);
    const token = authService.getToken();

    if(token){
      request = request.clone({
        setHeaders: { 'x-access-token' : token }
      });
    }
    
    return next.handle(request).do(
      (event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          const response:HttpResponse<any> = (<HttpResponse<any>> event);
          const authorizationHeader = response.headers.get('Authorization');
          console.log('Authorization header value: ' + authorizationHeader);
          if(authorizationHeader){
            authService.setToken(authorizationHeader);
          }
        }
      },
      (err: any) => {
      if (err instanceof HttpErrorResponse){
        if (err.status === 401) {
          this.router.navigate(['/']);
        }
      }
    });
  }
Brockie answered 7/11, 2017 at 10:47 Comment(5)
Could it be related with the content type? I see that you set the response type as "text" in your TS code, but then, the response itself comes as "application/json". Let me know if im wrong!Cortneycorty
Interresting indeed, I did not notice... I had to set the expected type to "string" as the API returns a null body, just a status 200 and a header containing the token. Let me try with rreturn an empty json if it works better.Brockie
Nope, doesn't help. No header other than 'Content-Type' can be read from the response.Brockie
I see... I'll take another lookCortneycorty
Ok, just found the reason. Using an answer to benefit from the formatting capabilities. Thanks for your time @avilac!Brockie
B
8

Ok, here is the answer: I'm using Node.js/Express as backend and even though headers are visible in the network tab of Chrome, it doesn't make them available for processing by Angular.

"How comes they're visible yet cannot be used?" is still not clear to me.

Configure your Node/Express app (look for the comment "SOLUTION HERE"):

function configureExpress(expressApp){  
    expressApp.use(bodyparser.urlencoded({ extended: true }));
    expressApp.use(bodyparser.json());

    // Middleware: Use CORS, add headers and allow methods
    expressApp.use(expressMiddleware);
}

function expressMiddleware(req, res, next) {

    // Request origin: Allow ALL
    res.header("Access-Control-Allow-Origin", "*");

    // Allowed headers
    res.header("Access-Control-Allow-Headers",

        "Origin"
        +",X-Requested-With"   // Dont allow AJAX CORS without server consent - see https://mcmap.net/q/54227/-what-39-s-the-point-of-the-x-requested-with-header
        +",x-access-token"
        +",Content-Type"
        +",Authorization"
        +",Accept"
    );

    // SOLUTION HERE
    // Allow headers access
    res.header("access-control-expose-headers",
        ",Authorization"
        +",Content-Length"
        );

    // Allowed methods
    res.header('Access-Control-Allow-Methods',
        'GET,'
        +',POST'
        +',OPTIONS'
        +',PUT,'
        +',DELETE'
    );

    // Handle CORS requests: cross-domain/origin requests will begin with an OPTION request to the same endpoint.
    if('OPTIONS' === req.method){
        res.sendStatus(200);
    }

    else{
        // Request validations complete
        next();
    }
}
Brockie answered 7/11, 2017 at 11:34 Comment(0)
G
0

Adding the answer with some more relevant information

I am using Angular 7 and just adding the custom header in Access-Control-Expose-Headers in the API didn't work for me.

The catch here is the custom header has to be in lower case as comma delimited values in Access-Control-Expose-Headers

Example (Express)

const cors = require('cors');

const corsOptions = {
    allowedHeaders: 'x-custom-one,x-custom-two',
    exposedHeaders: 'x-custom-one,x-custom-two'
}
app.use(cors(corsOptions));
Golub answered 9/6, 2019 at 18:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.