Monday, 17 April 2017

Configure Magento headers to call Rest API in Angular 2

Below are the steps to configure CORS (Cross Origin Resource Sharing) in the server(Magento) to call its API in frontend(Angular 2).


The below procedure is a fix for different CORS based error scenarios like:

1) Response for preflight has invalid HTTP status code 400

2) Response for preflight has invalid HTTP status code 405

3) No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://10.22.1.168:3000' is therefore not allowed access.

4) Request header field authorization is not allowed by Access-Control-Allow-Headers

5) Request header field content-type is not allowed by Access-Control-Allow-Headers



Step 1: In the .htacess of Magento,under <IfModule mod_rewrite.c>  replace

                  RewriteCond %{REQUEST_METHOD} ^TRAC[EK]
                  with
                  RewriteCond %{REQUEST_METHOD} OPTIONS

Step 2:
                  RewriteRule .* - [L,R=405]   with
                  RewriteRule ^(.*)$ $1 [R=200,L]

Step 3:
                  To the end of the page, there is another <IfModule mod_headers.c> (around line ~120)
                  Under this tag,add the below 2 lines:
             
                  Header always set Access-Control-Allow-Origin "*"

            Header always set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization"

Note: Dont forget the word 'always'.

Step 4:
                 Make sure to activate the apache module headers:
        a2enmod headers

After changes my .htaccess looks like:

... ....
 ....
<IfModule mod_rewrite.c>
Options +FollowSymLinks
    RewriteEngine on
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
#RewriteCond %{REQUEST_METHOD} ^TRAC[EK]
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule ^(.*)$ $1 [R=200,L]
#RewriteRule .* - [L,R=405]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-l
    RewriteRule .* index.php [L]
</IfModule>

...
...
ErrorDocument 404 /pub/errors/404.php
ErrorDocument 403 /pub/errors/404.php
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization"
Header always set X-UA-Compatible "IE=edge"
    <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|webmanifest|woff2?|xloc|xml|xpi)$">
        Header unset X-UA-Compatible
    </FilesMatch>
</IfModule>


My Frontend (Angular 2) logic looks something like:

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

export class ProductService {
    public _productUrl = 'http://10.22.1.168/Mage_ang2/index.php/rest/V1/customers/1';

    constructor(private _http: Http) { }

    getProducts(): Observable<IProduct[]>
{
let headers = new Headers({'Content-Type': 'application/json;charset=UTF-8',
'Authorization':'Bearer ntthnrbj1uam2tuv1ekva7n18mcnkby3'
});

 return this._http.get(this._productUrl,{headers:headers})
        .map(response => {
            return response.json();
        });
    }

The Authorization Bearer key was generated as below:

To get the bearer token:

1) Login to Magento admin, create a user role and a user in admin

2) To get bearer token :

    curl -X POST "http://<localhost/IP>/Magento2/index.php/rest/V1/integration/customer/token" -H "Content-Type: application/json" -d '{"username":"restuser_name","password": "restuser_password"}'

Explanation :


RewriteCond %{REQUEST_METHOD} OPTIONS 
RewriteRule ^(.*)$ $1 [R=200,L] 
Apache will respond "200 OK" when the request method is OPTIONS



Header set Access-Control-Allow-Origin "*"
Giving global cross-origin access to Magento. Usually its a bad idea to give * from a security perspective. 
Suggested way is to give the the origin(domain+scheme+port number) site URL in place of *.
Eg: Access-Control-Allow-Origin: http://siteA.com

ie) When Site A tries to fetch content from Site B, Site B can send an Access-Control-Allow-Origin response header to tell the browser that the content of this page is accessible to certain origins.
By default, Site B's pages are not accessible to any other origin; using the Access-Control-Allow-Origin header opens a door for cross-origin access by specific requesting origins.


Header always set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization
 Each header must explicitly be listed and I added x-test-header to Access-Control-Allow-Headers





References which were very useful:

https://benjaminhorn.io/code/setting-cors-cross-origin-resource-sharing-on-apache-with-correct-response-headers-allowing-everything-through/

http://stackoverflow.com/questions/32500073/request-header-field-access-control-allow-headers-is-not-allowed-by-itself-in-pr