Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpClient do not detect header params #20554

Open
JayendharPrakash opened this issue Nov 21, 2017 · 33 comments
Open

HttpClient do not detect header params #20554

JayendharPrakash opened this issue Nov 21, 2017 · 33 comments

Comments

@JayendharPrakash
Copy link

@JayendharPrakash JayendharPrakash commented Nov 21, 2017

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
@trotyl
Copy link
Contributor

@trotyl trotyl commented Nov 21, 2017

Sorry, your problem seems to be a different one, please provide a (valid) reproduction, you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5

@GAlexMES
Copy link

@GAlexMES GAlexMES commented Nov 24, 2017

+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

@alxhub
Copy link
Contributor

@alxhub alxhub commented Nov 28, 2017

I'll look into this, but logging the headers isn't going to show them correctly, as they're lazily parsed.

@arnehoek
Copy link
Contributor

@arnehoek arnehoek commented Nov 29, 2017

I get the lazy parsing, but res.headers.get('Authorization') also doesn't work for me though.

@trotyl
Copy link
Contributor

@trotyl trotyl commented Nov 29, 2017

@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.

@GAlexMES
Copy link

@GAlexMES GAlexMES commented Nov 29, 2017

As far as I see, it is also not possible to add a Header to a post or get request.

    let requestURL:string = "myurl";
    this.http.get(requestURL, { 
        headers: new HttpHeaders().set('Authorization', 'my-auth-token'),
    });

When I have a look using the Firefox Dev Tools, the Authorization Header is not present.

Environment

Angular version: 4.4.6
For Tooling issues:
- Node version: 5.3.0
- Platform:  Windows

Others:
@angular/cli: 1.4.0
node: 5.3.0
os: win32 x64
    "@angular/cdk": "^2.0.0-beta.12",
    "@angular/cli": "^1.4.0",
    "@angular/common": "^4.4.6",
    "@angular/compiler": "^4.4.6",
    "@angular/compiler-cli": "^4.4.6",
    "@angular/core": "^4.4.6",
    "@angular/forms": "^4.4.6",
    "@angular/http": "^4.4.6",
    "@angular/language-service": "^4.4.6",
    "@angular/material": "^2.0.0-beta.12",
    "@angular/platform-browser": "^4.4.6",
    "@angular/platform-browser-dynamic": "^4.4.6",
    "@angular/router": "^4.4.6",
@trotyl
Copy link
Contributor

@trotyl trotyl commented Nov 29, 2017

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

@GAlexMES
Copy link

@GAlexMES GAlexMES commented Nov 29, 2017

@trotyl
To your first comment:
I tried it also with just Access-Control-Expose-Headers but it didn't work. I played a little bit around with it. You are right, the screenshot is not good, I will change it tomorrow.

To your second comment:
I think that is not completly correct. For exaple the Access-Control-Request-Method is used by the Request. Anyway, in the second Example I want to show, that it is also not possible to add an Header to a Request, which should definitly be possible independent of CORS Headers. It maybe should be a different Issue, but I thought it may be related to this one.

@MyGitHubTests
Copy link

@MyGitHubTests MyGitHubTests commented Dec 2, 2017

I have exactly same issue - expecting to see custom headers in my response sent by our back-end server and they are not present in response.headers:

Code

import { Injectable } from '@angular/core';
import {
  HttpClient,
  HttpHeaders,
  HttpRequest,
  HttpResponse,
  HttpEvent,
  HttpEventType,
  HttpParams
} from '@angular/common/http';
....
  newsession() {
    let url: string = this.config.urlLogin + "session/new";

    let hdrs = new HttpHeaders({ 'Content-Type': 'application/json' });

    let param = { group: this.userAuth.group, user: this.userAuth.user, password: this.userAuth.password };

    return this.http.post(url, param, { headers: hdrs, observe: 'response' }).subscribe(res => {
      console.log(res);
      console.log(res.headers.get('X-Powered-By'));
    });
  }

Here's response - notice how number of headers:
image

Here's what's inside res.headers
image

package.json

{
  "name": "claim-cpr",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve --aot",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.0.3",
    "@angular/common": "^5.0.3",
    "@angular/compiler": "^5.0.3",
    "@angular/core": "^5.0.3",
    "@angular/flex-layout": "^2.0.0-beta.10-4905443",
    "@angular/forms": "^5.0.3",
    "@angular/http": "^5.0.3",
    "@angular/platform-browser": "^5.0.3",
    "@angular/platform-browser-dynamic": "^5.0.3",
    "@angular/platform-server": "^5.0.3",
    "@angular/router": "^5.0.3",
    "core-js": "^2.5.1",
    "font-awesome": "^4.7.0",
    "rxjs": "^5.5.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "^1.5.4",
    "@angular/compiler-cli": "^5.0.3",
    "@angular/language-service": "^4.2.4",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "~3.2.0",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.7.0",
    "typescript": "2.4.2"
  }
}

Clearly this is major bug,

@trotyl
Copy link
Contributor

@trotyl trotyl commented Dec 2, 2017

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

@MyGitHubTests
Copy link

@MyGitHubTests MyGitHubTests commented Dec 2, 2017

Where and how should I set them?

Why do they show up in browser network tab?

@AhHa45
Copy link

@AhHa45 AhHa45 commented Dec 19, 2017

@MyGitHubTests please try #5237 (comment)

@Yura13
Copy link

@Yura13 Yura13 commented Dec 21, 2017

@AhmedHasnaoui your solution fix this problem

@Bjeaurn
Copy link

@Bjeaurn Bjeaurn commented Jan 4, 2018

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.

@bertjan
Copy link

@bertjan bertjan commented Jan 4, 2018

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 ;-)

@trotyl
Copy link
Contributor

@trotyl trotyl commented Jan 4, 2018

@Bjeaurn @bertjan HttpClient & Interceptor came in 4.3.0, if it's an HttpInterceptor specific issue, didn't get how could it worked in 4.1.3?

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

XHR will not expose the OPTIONS request & response to user, this will not happen. Have you filtered the HttpEventType?

@bertjan
Copy link

@bertjan bertjan commented Jan 4, 2018

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.

@Bjeaurn
Copy link

@Bjeaurn Bjeaurn commented Jan 4, 2018

Our mistake, I'm sure you've cleared up most of the confusion in this issue already.

It works if you set the correct headers from your service and plug into the RESPONSE for both the HttpInterceptor and the HttpClient.

@mjohns39
Copy link

@mjohns39 mjohns39 commented Jan 10, 2018

@bertjan Would you mind posting your map function please from your comment? I am having the same issue and am not familiar with the pipe function.

@bertjan
Copy link

@bertjan bertjan commented Jan 10, 2018

@mjohns39 sure:

    intercept(req: HttpRequest<any>, next: any): Observable<any> {
        const newReq = req.clone({withCredentials: true, headers: req.headers.set('Content-Type', 'application/json')});
        return next.handle(newReq).pipe(
            map(response => this.processResponse(response)),
            catchError((err: HttpErrorResponse, caught: any) => this.handleErrors(req, err, caught))
        );
    }

Function processResponse(..) inspects the response headers (via response.headers.get('<name-of-the-header>'); and emits an event to an eventEmitter when further action is needed.

@mjohns39
Copy link

@mjohns39 mjohns39 commented Jan 10, 2018

Nevermind. I found it's a function in the rxjs library.

For those confused like me: import {map} from 'rxjs/operators'

Unfortunately, this didn't solve the problem for me. Stepping through, I am eventually able to see the Json body of the response from my rest api in the map method of the interceptor. I am also able to see the Access-Control-Expose-Headers set in the network tab of Chrome dev tools. But back in the map method of the interceptor, none of the Http headers are set.

@mjohns39
Copy link

@mjohns39 mjohns39 commented Jan 10, 2018

(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.

@duylampersol
Copy link

@duylampersol duylampersol commented Jan 23, 2018

How is it going ? I spent my day for this bug :(

@Bjeaurn
Copy link

@Bjeaurn Bjeaurn commented Jan 23, 2018

The main thing we did wrong was we didn't put the map operator on the Response, not the Request. So be careful about that.

@trotyl
Copy link
Contributor

@trotyl trotyl commented Jan 23, 2018

@duylampersol Most people on the thread have found it's due to their own faults, and there's still no reproduction from the remainings, if you can provide a repro, it'll be "solved" quicker.

@Bjeaurn
Copy link

@Bjeaurn Bjeaurn commented Jan 23, 2018

@trotyl @duylampersol I'm not sure if there's even a bug. The big difficulty with finding the fault here (on the dev's own side) is that there's a part of Server settings that is very super irrelevant for the angular side. You need to set your headers there properly, including the security headers that modern browsers require.

On the angular/common/http side with the HttpClient, all you need to check is if you're using .map() on the Response, not the Request that goes out first. In our application running @angular 5.1.1 this all works exactly as expected.

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Feb 26, 2018
@lenichols
Copy link

@lenichols lenichols commented Dec 12, 2018

Still having an issue with this even with the response headers showing. I am unable to use the headers appearing in console or pass them along.

@efikatec
Copy link

@efikatec efikatec commented Jan 9, 2019

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).

@Bjeaurn
Copy link

@Bjeaurn Bjeaurn commented Jan 9, 2019

Nice @efikatec, I think the main point in regards to Angular is that you need to set these headers on the Server side, not within Angular. So I'm not sure how relevant your specific example is, but it's a good idea to show what to look for in your environment.

@rbm4
Copy link

@rbm4 rbm4 commented Jan 17, 2019

@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.

Solved my problem, i've added both headers in the "OPTIONS" and "POST" responses, angular have managed to read all of then

I'm using Spring boot, in case someone wants to know, you just need to filter the requests by its methods, in my case, this is my method which generates a JWT token and passes it to the response's header

The OPTIONS request (this method filters all authorized requests using Spring Security package):

protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
    	// Do not filter any OPTIONS request
        if (request.getMethod().equalsIgnoreCase("OPTIONS")){
                // Allow Custom headers
    		response.addHeader(CUSTOM_HEADER_ALLOW, "*");
    		return false;
    	} else if (request.getMethod().equalsIgnoreCase("POST")) {
    		return true;
    	} else {
    		return true;
    	}
    }

The POST request:

static final String TOKEN_PREFIX = "Bearer";
	static final String HEADER_STRING = "Authorization";
	static final String HEADER_CUSTOM_HEADER = "Access-Control-Expose-Headers";
	
	static void addAuthentication(HttpServletResponse response, String credentials_json) {
                // Generating Token
		String JWT = Jwts.builder()
				.setSubject(credentials_json)
				.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
				.signWith(SignatureAlgorithm.HS512, SECRET)
				.compact();
		// Setting token to the header
		response.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT);
                // Allowing Angular read all the headers
		response.addHeader(HEADER_CUSTOM_HEADER, "*");
	}
@Bjeaurn
Copy link

@Bjeaurn Bjeaurn commented Jan 17, 2019

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?

@akanurlan0
Copy link

@akanurlan0 akanurlan0 commented Sep 17, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.