angular: HttpClient do not detect header params

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

HttpClient do not detect the custom header params that are sent from the API. While using Http we were able to access it and make use of it.

Expected behavior

HttpClient should detect the custom headers and make them accessible by the code. We tried with both Http & HttpClient. I works fine with Http but not with HttpClient.

Minimal reproduction of the problem with instructions

We used both Http & HttpClient to fetch the header params. Http returns the header, but HttpClient doesn’t.

constructor(private http: Http, private httpClient: HttpClient) {}
getContent() {
        const url = '<API URL>';
        return this.http.post(url, data)
            .map((res: Response) => {
                console.log('http content', res);
            });


       return this.httpClient.post(url, data, { observe: 'response' })
            .map((res: HttpResponse<any>) => {
                console.log('httpClient content',res);
            });
}

When checked in console, http returns the response with headers but httpClient returns an empty array. When checked in networks tab of browser inspector, it displays all the header parameters that are sent from API.

The server has the following CORS options: origin: ‘*’, methods: ‘GET,HEAD,PUT,PATCH,POST,DELETE’, allowedHeaders: ‘Origin,X-Requested-With,x-access-token,Content-Type,Authorization,Accept,tokenkey’, exposedHeaders: ‘Content-Type,Authorization,tokenkey’

What is the motivation / use case for changing the behavior?

I want to access the token key that is sent from the API and store it to authorize and validate the user. I am unable to get the tokenkey from the API.

Environment


Angular version: 4.4.6
I created a dummy project with 5.0.0 as well. It didn't work.

Browser:
- [x] Chrome (desktop) version 62.0.3202.94
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 6.11.2
- Platform:  Windows

Others:
@angular/cli: 1.4.9
node: 6.11.2
os: win32 x64
@angular/animations: 4.4.6
@angular/common: 4.4.6
@angular/compiler: 4.4.6
@angular/core: 4.4.6
@angular/forms: 4.4.6
@angular/http: 4.4.6
@angular/platform-browser: 4.4.6
@angular/platform-browser-dynamic: 4.4.6
@angular/router: 4.4.6
@angular/service-worker: 1.0.0-beta.16
@angular/cli: 1.4.9
@angular/compiler-cli: 4.4.6
@angular/language-service: 4.4.6
typescript: 2.3.4

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 14
  • Comments: 38 (11 by maintainers)

Most upvoted comments

@GAlexMES From the screenshot you’re definitely misusing it.

Access-Control-Allow-Headers must be provided in response of OPTIONS request (pre-flight); Access-Control-Expose-Headers must be provided in response of POST request.

They’ll never show up in same response.

+1 It also doesn’t work with an angular 5.0.0 Projekt and Firefox 57.0.

    var body:string = "";
    this.http
      .post(url, bod,y {responseType:'text', observe: 'response'})
      .subscribe(
        (res: HttpResponse<any>) => {
                console.log(res.headers.keys);
        }
      );
    }

The console shows something like:

HttpHeaders.prototype.keys() length: 0 name: “” prototype: Object { … } proto: function ()

But the Firefox Dev Tools shows the following Response Headers: grafik

OK, found the problem. Caused by ourselves, of course 😉

We were inspecting the REQUEST headers while we should instead have been looking at RESPONSE headers.

This works (in the intercept(…) method):

        return next.handle(req).pipe(
            map(resp => {
                if (resp.headers) {
                    console.log('interceptor header keys: ', resp.headers.keys());
                    console.log('interceptor X-Service-Name: ', resp.headers.get('X-Service-Name'));
                }
                return resp;
            }),
            catchError((err: HttpErrorResponse, caught: any) => this.handleErrors(req, err, caught))
        );

The reason for our confusion is probably that (from our point of view) the signature of intercept(…) changed a bit. In 4.1.3 (custom intercept method that we created ourselves): intercept(observable: Observable<Response>): Observable<Response> { In 5.1.1: intercept(req: HttpRequest<any>, next: any): Observable<any> {

So previously we were only working with an http response in our interceptor, now we both have the http request and the ‘next’ chain. When inspecting the http response in an interceptor, we should (obviously…) hook into the next.handle(…) pipe.

I’m afraid I’m seeing similar issues after moving to Angular 5.1.1

    intercept(req: HttpRequest<any>, next: any): Observable<any> {
        const version = req.headers.get('X-Service-API-Version');
        const serviceName = req.headers.get('X-Service-Name');
        if (version && serviceName && !this.validVersion(parseInt(version, 10), serviceName)) {
            this.events.emit(ERROR_EVENT, this.notification);
        }

Both version and serviceName stay empty.

One of the calls that is expected to be valid in this case and start checking for validVersion looks like this: Request

POST /log HTTP/1.1
Host: [omitted]
Connection: keep-alive
Content-Length: 385
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: http://core.test
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Content-Type: application/json
DNT: 1
Referer: http://core.test/
Accept-Encoding: gzip, deflate, br
Accept-Language: nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: [omitted]

Response

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type, body
Access-Control-Allow-Methods:POST,PUT,DELETE
Access-Control-Allow-Origin:http://core.test
Access-Control-Expose-Headers:X-Service-API-Version, X-Service-Name
Access-Control-Max-Age:1000
Connection:keep-alive
Content-Encoding:gzip
Content-Type:application/json;charset=UTF-8
Date:Thu, 04 Jan 2018 08:26:19 GMT
Server:nginx
Strict-Transport-Security:max-age=63072000; includeSubdomains
Transfer-Encoding:chunked
X-Application-Context:service-frontend-log:dev,override
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-Service-API-Version:1
X-Service-Name:service-frontend-log
X-XSS-Protection:1; mode=block

This seems to be matching the XMLHttpRequest standard for required headers, yet I’m seeing nothing but nulls in my console when doing this:

intercept(req: HttpRequest<any>, next: any): Observable<any> {
        const version = req.headers.get('X-Service-API-Version');
        const serviceName = req.headers.get('X-Service-Name');
        console.log('version', version);
        console.log('serviceName', serviceName);

result: version null serviceName null

This seemed to work when we were using version Angular 4.1.3, where we received results similar to this when testing this specific service call:

version 1 serviceName service-frontend-log

Any ideas @trotyl? We’ve been careful to examine if we weren’t missing any headers to make this work but couldn’t spot any. We’re afraid we’ve actually found a regression bug between Angular 4.1.3 and Angular 5.1.1.

@MyGitHubTests You did not even set Access-Control-Expose-Headers header, how should it work?

Fixed by specifying observe: response in request’s options. More details: https://angular.io/guide/http#reading-the-full-response

Can we close this issue, I’m quite sure there is no issue anymore. You can parse headers as of now and the main issues people keep talking about are CORS related on their own backends, not in the Angular frontend.

@JayendharPrakash Can you close this as resolved please?

For those this could help. I forgot to set ‘access-control-expose-headers’ in the server. This is my extract of expressjs returning the answer of a login post async (req: Request, res: Response, next: NextFunction) => { … … return res .header(‘access-control-expose-headers’, ‘x-auth-token’) .header(‘x-auth-token’, ‘cocco’) .status(200) .send(account); }

Another way of doing this (when using expressjs) is to set the cors as such const corsOptions: cors.CorsOptions = { … exposedHeaders: [‘x-auth-token’] }; … const instance: Express = express(); instance.use(require(‘cors’)(corsOptions)); …

I prefer the first method as this gives me choice when that header is allowed (and used).

It does seem to work on regular HttpClient calls, but not in an HttpInterceptor. We’re on Angular 5.1.1.

Http call with HttpClient:

    getActiveSession(): Observable<any> {
        return this.http.get(this.endpoints.get('session.get'), {observe: 'response'}).map(resp => {
            console.log('service header keys: ', resp.headers.keys());
            console.log('service X-Service-Name: ', resp.headers.get('X-Service-Name'));
            return resp;
        });
    }

HttpInterceptor code (part of it):

intercept(req: HttpRequest<any>, next: any): Observable<any> {
        console.log('interceptor header keys: ', req.headers.keys());
        console.log('interceptor X-Service-Name: ', req.headers.get('X-Service-Name'));

Http response from the backend:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type, body, X-Service-API-Version, X-Service-Name
Access-Control-Allow-Methods:POST,PUT,DELETE
Access-Control-Allow-Origin:http://core.test
Access-Control-Expose-Headers:Content-Type, body, X-Service-API-Version, X-Service-Name
Access-Control-Max-Age:1000
Connection:keep-alive
Content-Type:application/json
Date:Thu, 04 Jan 2018 08:48:04 GMT
Transfer-Encoding:chunked
X-Service-API-Version:6
X-Service-Name:test-service

Console logging:

interceptor header keys:  []
interceptor X-Service-Name:  null
interceptor header keys:  ["Content-Type"]
interceptor X-Service-Name:  null
service header keys:  (3) ["x-service-api-version", "content-type", "x-service-name"]
service X-Service-Name:  test-service

(that the request passes through the interceptor twice: once as OPTIONS pre-flight and once as GET)

The logging from the HttpClient call includes Content-Type and our custom response headers. The logging from the HttpInterceptor only includes the Content-Type response header. As @Bjeaurn indicated, this did work on Angular 4.1.3.

Giving it a bit more thought, I’m wondering. Do we have access to both the http request and http response in an interceptor? We might be looking at the request instead of the response 😉

All CORS headers are added in response, that’s not related to Angular, and you’re adding them in wrong place.

I’m going to leave this issue open for now in a “needs reproduction” state, but I expect @Bjeaurn is correct - this is largely related to CORS issues and header visibility, and not a bug in Angular.

If anyone has a reproduction showing headers which should be available on the response per CORS but are missing, please add it here and we can look into it. Otherwise, this issue will be closed eventually.

(apologies in advance for formatting as I am on mobile) I figured it out.

The map method of the response interceptor :

import {map} from ‘rxjs/operators; import {HttpResponse} from’ @angular/common/http; … return next.handle(req).pipe( map((resp: HttpResponse<any>) => { if(resp.headers) { //your logic for headers } }) ) ; …

*Note that you still have to set the Access-Control-Expose-Headers header explicitly on the backend.