12

I have this function that I use for login requests.

private login(params: LoginParams): Promise<any> {
    const loginHeaders: HttpHeaders = new HttpHeaders()
        .set('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
        .set('site', 'first');

    const loginCredentials = new HttpParams()
        .set('j_username', params.username)
        .set('j_password', params.password);

    const requestUrl = this.appConfig.baseUrl + 'restoftheurl';

    return this.http
        .post(requestUrl, loginCredentials.toString(),
            {headers: loginHeaders, responseType: 'text'})
        .toPromise();
  }

If the password has a plus sign (+) in it, it is encoded into a space sign and then the request fails being a bad credential. How do I preserve the plus sign? What am I doing wrong?

Anjil Dhamala
  • 1,544
  • 3
  • 18
  • 37
  • 2
    You need to URL encode it to `%2B`, `+` does mean space in URLs (see e.g. https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20). Although then you get hit by this bug: https://github.com/angular/angular/issues/18261 – jonrsharpe Nov 29 '18 at 20:03
  • Oh! So it is a bug. I thought I was going crazy. – Anjil Dhamala Nov 29 '18 at 20:09
  • No it's not a bug, any special characters in url string are considered unsafe and may compromise the web servers, think of malicious scripts from hackers in url. – nircraft Nov 29 '18 at 20:11
  • Just found I have almost duplicated: https://stackoverflow.com/questions/45428842/angular-url-plus-sign-converting-to-space/52458069#52458069 – Anjil Dhamala Nov 29 '18 at 20:16

3 Answers3

24

This is also an Angular issue (@angular/common/http)

It will interpret the raw + sign as a replacement for a space.

You can implement HttpParameterCodec into a simple encoder, for example:

import {HttpParameterCodec} from "@angular/common/http";
export class HttpUrlEncodingCodec implements HttpParameterCodec {
    encodeKey(k: string): string { return standardEncoding(k); }
    encodeValue(v: string): string { return standardEncoding(v); }
    decodeKey(k: string): string { return decodeURIComponent(k); }
    decodeValue(v: string) { return decodeURIComponent(v); }
}
function standardEncoding(v: string): string {
    return encodeURIComponent(v);
}

And then use it to get encoded correctly:

const headers = new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'});
const params = new HttpParams({encoder: new HttpUrlEncodingCodec()});
http.post(url, params, {headers: this.headers});
MyNameIsTrez
  • 117
  • 2
  • 13
Vy Nguyen
  • 296
  • 2
  • 6
  • 1
    Looks all over for a solution and this one worked. Thank you so much – emarel Mar 14 '20 at 17:16
  • I think you can use [HttpUrlEncodingCodec](https://angular.io/api/common/http/HttpUrlEncodingCodec) instead of implementing it yourself. – Ron Inbar May 17 '21 at 13:48
  • 1
    Correction: the built-in `HttpUrlEncodingCodec` doesn't work as it also replaces `+` with a space. – Ron Inbar May 17 '21 at 14:00
3

Just use encodeURIComponent to encode the password before sending it.

private login(params: LoginParams): Promise < any > {

  ...

  const loginCredentials = new HttpParams()
    .set('j_username', params.username)
    .set('j_password', encodeURIComponent(params.password));

  ...
}

NOTE: On your API end, you'll have to use decodeURIComponent(yourPasswordParam) to get the actual password.

UPDATE:

Just try it right here and see what it gives on encoding:

var encodedUsername = encodeURIComponent('mclovin+');
console.log('Encoding Username gives: ', encodedUsername);
console.log('NOT mclovin%252B');

var encodedPassword = encodeURIComponent('fogell+');
console.log('Encoding Password gives: ', encodedPassword);
console.log('NOT fogell%252B');
SiddAjmera
  • 38,129
  • 5
  • 72
  • 110
  • 1
    I tried this before. Unfortunately, it double encodes the credentials and ultimately fails as a bad credentials request. Example: username: mclovin+ and password: fogell+ becomes encoded as mclovin%252B and fogell%252B. This probably has to do with the Angular bug @jonrsharpe mentioned in his comment. – Anjil Dhamala Nov 29 '18 at 20:21
  • That's strange. Not sure why it's double encoding the credentials. Which browser are you using? – SiddAjmera Nov 29 '18 at 20:30
  • 1
    Might be because Angular itself is encoding HttpParams when it shouldn't? I use Chrome, btw – Anjil Dhamala Nov 29 '18 at 21:05
  • Angular HttpParams default encoder is broken ... it doesn't properly encode plus character, issues: https://github.com/angular/angular/issues/33728 and https://github.com/angular/angular/issues/18261 – Colin D Bennett Jan 30 '20 at 21:27
1

If you are trying to send it as part of URL, this has to be encoded using encodeURIComponent.

Seeing your code you are adding the password and username in HTTP params which will be shown in request url.

If you do not want to show username and password as part of url query string, you can send it as request body for the http call and you will, not need to do encodeURIComponent.

EX:console.log(encodeURIComponent('?x=test'));

console.log(encodeURIComponent('+test'));
nircraft
  • 8,242
  • 5
  • 30
  • 46