9

I have seen several other questions like this, but they do not solve the issue. I used MailChimp's API to make a simple call to add a member to my mailing list when they sign up.

However when I test, I get a 401 unauthorized and the API complains of no API key submitted. When I inspect the request in Chrome, I don't see any Authorization header. Here is my code:

        const formData = {
            email_address: this.emailControl.value,
            status: 'subscribed',
            merge_fields: {
                NAME: this.nameControl.value
            }
        };
        const header = new HttpHeaders({
            'Authorization': 'apikey:' + environment.mailChimpApiKey
        });

        this.http.post(this.mailChimpUrl, formData, {
            headers: header,
            observe: 'response'
        }).subscribe(response => {
            console.log('response', response);
            if (response.status === 200) {
                this.submitted = true;
            }
        });

I have checked and double-checked the HttpClient.post method signature, and how the MailChimp API expects to receive the Auth header. It seems like I'm doing everything right, so why isn't Angular setting the header?

I am noticing that the value changes only for the Access-Control-Request-Headers when I set optional headers. Am I reading the chrome console wrong?

enter image description here

Angular version: 5.2.4

inorganik
  • 24,255
  • 17
  • 90
  • 114

4 Answers4

4

The issue is not with Angular. Modern browsers send a preflight OPTIONS request for most cross-domain requests, which is not supported by Mailchimp. The Mailchimp API does not support client-side implementations:

MailChimp does not support client-side implementation of our API using CORS requests due to the potential security risk of exposing account API keys.

It would have been nice if this was stated a bit more obviously, but I didn't notice it at first. The best solution is to use jsonp.

inorganik
  • 24,255
  • 17
  • 90
  • 114
3

To do this we need to import Headers and RequestOptions along with Http from @angular/http library.

And as @Maciej suggested you can also use withCredentials : true to your request options.

ApplicationService.ts

import { Injectable } from '@angular/core';
import { Http, Headers, Response, RequestOptions } from '@angular/http';

@Injectable()
export class ApplicationService {

  constructor(private http: Http) {
  }

  myformPost(id:number, formData : any){

     let header = this.initHeaders();
     let options = new RequestOptions({ headers: header, method: 'post'});
     let body = JSON.stringify(formData);

     return this.http.post(this.myapiUrl, body, options)
                .map(res => {
                    return res.json();
                })
                .catch(this.handleError.bind(this));
  }

  private initHeaders(): Headers {
      var headers = new Headers();
      let token = localstorage.getItem(StorageKey.USER_TOKEN);
      if (token !== null) {
         headers.append('Authorization', token);
      }

      headers.append('Pragma', 'no-cache');
      headers.append('Content-Type', 'application/json');
      headers.append('Access-Control-Allow-Origin', '*');
      return headers;
  }

  private handleError(error: any): Observable<any> {
      return Observable.throw(error.message || error);
  }             
}
Amol Bhor
  • 2,322
  • 1
  • 12
  • 14
0

From the screenshot, I assume that you are making OPTIONS request, not POST.

Probably the problem is on the server-side. Check out this answer: http post - how to send Authorization header?

You should also consider adding withCredentials to your request options.

const formData = {
            email_address: this.emailControl.value,
            status: 'subscribed',
            merge_fields: {
                NAME: this.nameControl.value
            }
        };
        const header = new HttpHeaders({
            'Authorization': 'apikey:' + environment.mailChimpApiKey
        });

        this.http.post(this.mailChimpUrl, formData, {
            headers: header,
            observe: 'response',
            withCredentials: true
        }).subscribe(response => {
            console.log('response', response);
            if (response.status === 200) {
                this.submitted = true;
            }
        });

According to documentation:

withCredentials: boolean | null

Enable use credentials for a Request.

Maciej Treder
  • 11,866
  • 5
  • 51
  • 74
  • Weird. It's still not adding the header. Or any other test header that I try. Are there any other weird gotchas I should look out for? – inorganik Mar 19 '18 at 16:09
  • I am just checking https://angular.io/guide/http#adding-headers and in my opinion everything is OK with your request... – Maciej Treder Mar 19 '18 at 16:16
  • It's stupid, but maybe you need to create RequestOptions separately, not "on the fly". Or at least 'cast' this object {headers: 'someHeader'} as RequestOptions ? – Maciej Treder Mar 19 '18 at 16:19
  • I added a screenshot, it is not actually setting headers, but values inside `Access-Control-Request-Headers` which seems like a clue – inorganik Mar 19 '18 at 16:19
  • So you are making OPTIONS, not POST :). Let me update my answer... – Maciej Treder Mar 19 '18 at 16:21
  • Yes, it is making an OPTIONS request. How to I make angular send it as POST request? I am using the post method. This HttpClient is chock full of gotchas. – inorganik Mar 19 '18 at 16:29
  • The issue is not on the Angular side. Check out this article: http://restlet.com/company/blog/2015/12/15/understanding-and-using-cors/ – Maciej Treder Mar 19 '18 at 16:31
  • I would argue it is on the angular side. I cannot make the mailchimp api accept an options request. curl post requests work fine. If angular did what I wanted it to do, this would work fine. In any case thank you very much for helping me pin this down! – inorganik Mar 19 '18 at 16:34
  • So your back-end and front-end need to be within the same domain. – Maciej Treder Mar 19 '18 at 16:35
  • Using a third-party API's that you can't control is an everyday use case. Angular is forcing me to make a proxy endpoint to do something so simple. What a waste of time! This is a detriment to using Angular. – inorganik Mar 19 '18 at 16:36
  • 2
    It's not Angular. It is browser :) – Maciej Treder Mar 19 '18 at 16:37
0

I'd very much prefer to comment on the original posters answer as I feel it is correct and a clarification comment may help that person get +1 for answer.

So..

If I am running code between angular and two of my domains and using say node to serve the data, I have to enter the following during development in the node server. I do this very open at first to just make sure all is working in dev.

app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, 
PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', true); //
next();

});

This clears up your error but is too open for cross scripting, you can read on how to allow specific domains etc to access it instead of the * provided here.

Alex Mackinnon
  • 300
  • 3
  • 12