angular: HttpClient.post body param is not sent correctly when using 'application/x-www-form-urlencoded' content type request header

I’m submitting a…


[x] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  

Current behavior

When sending http post request (using HttpClient) with body data (for example a json object), it looks that Angular send the json object as a “key” of another object.

For example, when doing this.httpClient.post('https://localhost/login', JSON.stringify({username, password}), if username is equal to (for example) ‘user’ and password is equal to ‘pass’, the request object in the server side will contain a body value like this: {{"username": "user", "password": "pass"}:""}

Here is a screenshot of what we can see as the value of “body” in request object, in server-side,

image

As you can see, the body object I’m trying to send with HttpClient.post seems to be the “key” of another Object wrapped around it, with an empty value…But it should be an object

Expected behavior

Angular should send body object as an object, and not as the key of another object

Minimal reproduction of the problem with instructions

Here is the code of my authentication service:

import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthenticationService {
    public token: string;

    constructor(private httpClient: HttpClient) {
        // set token if saved in local storage
        const currentUser = JSON.parse(localStorage.getItem('currentUser'));
        this.token = currentUser && currentUser.token;
    }

    login(username: string, password: string): Observable<boolean> {
        return this.httpClient.post('https://localhost/login', JSON.stringify({username, password}), {
          headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
        }).map((response: Response) => {
          // login successful if there's a jwt token in the response
          const token = response.json() && response.json().token;
          if (token) {
              // set token property
              this.token = token;

              // store username and jwt token in local storage to keep user logged in between page refreshes
              localStorage.setItem('currentUser', JSON.stringify({ username, token }));

              // return true to indicate successful login
              return true;
          } else {
              // return false to indicate failed login
              return false;
          }
        });
    }

    logout(): void {
        // clear token remove user from local storage to log user out
        this.token = null;
        localStorage.removeItem('currentUser');
    }
}

In the server side, I’m using Node.js (v8.4.0) with Express (v4.16.0)

Environment


Angular version: 4.4.4


Browser:
- [X] Chrome (desktop) version XX
- [ ] 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: 8.4.0  
- Platform:  Windows 

Others:

Thank you in advance for your help

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 29 (1 by maintainers)

Most upvoted comments

If i do it like that, it works fine for me.

` const body = new HttpParams() .set(‘client_id’, Settings.client_id) .set(‘client_secret’, Settings.client_secret) .set(‘grant_type’, ‘password’) .set(‘scope’, Settings.scope) .set(‘username’, auth.username) .set(‘password’, auth.password);

return this.http.post(Settings.iam_service_base_url + 'connect/token', body.toString(), {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  })

`

Hello @TitaneBoy,

I agree with some of the other commenters here - you cannot use application/x-www-form-urlencoded as your Content-Type and send a JSON encoded body. This is essentially lying to the server, and telling it to parse the body using a different format from the one it’s actually encoded in.

What’s happening is that your server-side framework is interpreting the JSON string as one long parameter name, with an empty value. Either encode your body in application/x-www-form-urlencoded format (pass an HttpParams instance as the body), or use application/json.

I have the same problem with Angular 4.4.4 😦

I have the same problem with Angular 4.4.4

Why do you JSON.stringify the object?

@fk-mbc I’ve just removed the “next()” instruction in my “.options” function and It works well now (with ‘application/json’ content type only…)

Web-client page

image

Client-Side

image

Server-side

image

It can be a workaround for now but overall, it is not working at all with ‘application/x-www-form-urlencoded’ content type header. I will rename the title of this issue to make focus of this aspect

In the example he is using ‘application/x-www-form-urlencoded’ so we have to use form encoding. I have created my own HttpFormEncodingCodec this is what I am doing to encode/decode properly.

import { HttpParameterCodec } from '@angular/common/http';

export class HttpFormEncodingCodec implements HttpParameterCodec {
    encodeKey(k: string): string { return encodeURIComponent(k).replace(/%20/g, '+'); }

    encodeValue(v: string): string { return encodeURIComponent(v).replace(/%20/g, '+'); }

    decodeKey(k: string): string { return decodeURIComponent(k.replace(/\+/g, ' ')); }

    decodeValue(v: string) { return decodeURIComponent(v.replace(/\+/g, ' ')); }
}

.....................
doPost(description, name) {
    headers: HttpHeaders = new HttpHeaders()
        .append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    const body = new HttpParams({ encoder: new HttpFormEncodingCodec() })
        .append('description', description)
        .append('name', name)
        .toString();

    return this.http.post<any>(this.apiUrl, body, { headers: headers });
}

If you are going to use the HttpFormEncodingCodec several times, you should create it only once in the class constructor to reuse it. Hope it helps

PS: I guess that HTTPClient should implements both encoders (url & form) and create a FormParams to be used instead HttpParams if you want to use ‘application/x-www-form-urlencoded’

@TitaneBoy i am having the same probleme as you i am stuck in OPTIONS http://localhost:3000/user/login net::ERR_CONNECTION_REFUSED here is some photo of the server and the front end side 1 2 and can you tell me what do you use to debug like that in node js and thnx

@TitaneBoy , sir if my api has multiple route how do I deal with OPTIONS method problem , Should I need to create options route for each post request as You created for login in your application is that right solution for this problem ??? Thanks

@fk-mbc …Thank you again for your suggestions and help 😃

Hi @fk-mbc

Thank you for you great detailed post. In my case, my nodejs server side is already configured with the same configuration you described before ( app.use(bodyParser.urlencoded({ extended: true }));). I am using bodyParser.json() as well

image

And as you said, with “application/x-www-form-urlencoded” in request headers, I have NO issue when using POSTMAN and I receive correctly in the server-side the data sended in the body request.

image

Here is the data I receive in server-side: image

But when using HttpClient.post from Angular, the body data received in the server-side are ugly and as described before in this issue.

So…The only thing I can confirm now is that it works well with POSTMAN but not with HttpClient

this.httpClient.post('https://localhost/login', JSON.stringify({username, password}) and this.httpClient.post('https://localhost/login', {username, password}) will not have the same behaviour. Are you sure that the latter won’t solve your problem?