Search code examples
phpangularcorsangular2-services

How to solve Cross-Origin-Request-Block issue while fetching data using angular2 and php?


Please read the question in detail since it is a long one with various edits and extended updates as per the request of other users.

I am trying to send data to php file using angular2. I am doing the angular project in Unix and the var/www/html/ is the php Xampp folder locations for running the php files.

My folder structure is like this:-

var/www/html/
        |_(angproject)
                |_(phpscript)
                |       |_login.php
                |_(src)
                   |_(app)
                        |_(admin)
                        |   |_(login)
                        |   |   |_login.component.ts
                        |   |
                        |   |_admin.component.ts
                        |
                        |_(_admin_service)
                        |       |_admin.login.ts
                        |
                        |_(_animations)
                        |
                        |_(front)
                        |
                        |_(_models)
                        |   |_admin.model.ts
                        |
                        |_app.module.ts

My app.module.ts file is like this:-

import { HttpModule, Http, Response, Headers, RequestOptions} from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { APP_BASE_HREF } from '@angular/common';
import { CanActivate } from "@angular/router";


import { AppComponent } from './app.component';
import { FrontComponent } from './front/front.component';
import { AdminComponent } from './admin/admin.component';
import { LoginComponent } from './admin/login/login.component';
import { DashboardComponent } from './admin/dashboard/dashboard.component';
import { HeaderComponent } from './admin/header/header.component';
import { FooterComponent } from './admin/footer/footer.component';
import { LeftmenuComponent } from './admin/leftmenu/leftmenu.component';
import { NavbarComponent } from './admin/navbar/navbar.component';
import { ShortcutbarComponent } from './admin/shortcutbar/shortcutbar.component';
import { AdminLoginService } from './_admin_service/admin.login';

const appRoutes: Routes = [
  { path: 'admin',
    component: AdminComponent,
    children: [
          { path: '', component: LoginComponent},
          { path: 'dashboard', component: DashboardComponent}
    ]
  }
];

@NgModule({
  declarations: [
    AppComponent,
    FrontComponent,
    AdminComponent,
    LoginComponent,
    DashboardComponent,
    HeaderComponent,
    FooterComponent,
    LeftmenuComponent,
    NavbarComponent,
    ShortcutbarComponent
  ],
  imports: [
    HttpModule,
    HttpClientModule,
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
  ],
  providers: [{provide: APP_BASE_HREF, useValue : '/' },AdminLoginService],
  bootstrap: [AppComponent]
})
export class AppModule { }

My login.component.ts file is this:-

import { Component, OnInit, Input } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

import { fadeInAnimation } from '../../_animations/index';
import { Admin } from '../../_models/admin.model';
import { AdminLoginService } from '../../_admin_service/admin.login';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css'],
  animations: [fadeInAnimation],
  host: { '[@fadeInAnimation]': '' },
  providers: [AdminLoginService]
})

export class LoginComponent implements OnInit {
    loading = false;
    returnUrl: string;
    responseStatus:Object= [];
    status:boolean ;
    //@Input() admin:Admin;

   model = new Admin('', '', '', 'Emailsss','Passwordsss'); 

    constructor(
        private route: ActivatedRoute,
        private router: Router,
        private _adminLogin: AdminLoginService
    ){}

  submitPost()
  {        
      //console.log("submit Post click happend " + this.model.email)
      //console.log(this.model);
      this._adminLogin.postLogin(this.model).subscribe(
         data => console.log(this.responseStatus = data),
         err => console.log(err),
         () => console.log('Request Completed')
      ); 
      this.status = true;       
  }

    ngOnInit() {

    }
}

The service file admin.login.ts file is this:-

import { Http, Response, Headers, RequestOptions} from '@angular/http';
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import 'rxjs/add/operator/map';
import { Admin } from '../_models/admin.model';

@Injectable()
export class AdminLoginService {
    http : Http;
    actionUrl : string;
    admin_login_Url: string;
    postData: Admin;


    constructor(public _http: Http) {
       this.http = _http;
       this.admin_login_Url = 'http://localhost/angproject/phpscript/login.php';
    }
    postLogin(postData:Admin) {
        let headers = new Headers();
        headers.append("Access-Control-Allow-Origin","*");
        headers.append("Access-Control-Allow-Methods","GET, POST");
        headers.append("Content-Type","application/json");

        let options = new RequestOptions({ headers: headers });
        console.log(postData);

        this.actionUrl = this.admin_login_Url;
        return this.http.post(this.actionUrl, {postData}, options)
          .map(res => res.json());          
    }
 }

And ultimately, my login.php file is this:-

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST");
header("Access-Control-Allow-Headers: *");
header("Content-Type: application/json; charset=utf-8");
include('connection.php');

$rawData    = file_get_contents("php://input");
$data       = json_decode($rawData, true);

$error = array();
if(isset($data['postData']['email']) && !empty($data['postData']['email']))
    $email = $data['postData']['email'];
else 
    $error[] = "Email was not entered";
if(isset($data['postData']['password']) && !empty($data['postData']['password']))
    $password = $data['postData']['password'];
else 
    $error[] = "Password was not entered";

if(empty($error))
{
    $runQuery = "SELECT * FROM users WHERE email = '$email' AND password = '$password'";
    $result = $conn->query($runQuery);
    if ($result->num_rows > 0) 
    {
        $response['status'] = 1;
        $response['message'] = "Login successfully";
        $response['error'] = 0;
    } 
    else 
    {
        $response['status'] = 0;
        $response['message'] = "An error occured while logging in";
        $response['error'] = $conn->error;
    }
}
else
{
    $response['status'] = 0;
    $response['message'] = "Parameter missing";
    $response['error'] = $error;
}
$respond = json_encode($response);
echo $respond;
exit;
?>

Now here is the problem. When testing in Chrome, clicking the submit button once is firing calling for the php script (Ajax) twice. The first call doesn't send any data and hence it shows validation message in return response. The second call sends the form's data and hence fetching the desired result by matching the data sent.

enter image description here

In case of firefox, I am getting this response:-

enter image description here

enter image description here

How can I solve it?

Note:

Here are the Request Headers of the "first call" in chrome:

enter image description here enter image description here

Here are the Request Headers of the "second call" in chrome: enter image description here enter image description here

EDIT: Dated- 04-04-2018:

As per the suggestions from David, I made subsequent changes in my login.php file:

header("Access-Control-Allow-Origin: http://localhost:4200");
header("Access-Control-Allow-Headers: application/x-www-form-urlencoded");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");

In the admin.login.ts file, I made the following changes:-

postLogin(postData:Admin) {
  let headers = new Headers();
  headers.append("Accept", "q=0.8;application/json;q=0.9");
  this.actionUrl = this.admin_login_Url;
  return this.http.post(this.actionUrl, {postData}, { headers: headers })
          .map(res => res.json()).share();
    }
}

Question 1: For the above code, I took reference from this site. Yet, I am still getting the error. How can I fix this in Firefox?

In the above code, I added the line headers.append("Accept", "q=0.8;application/json;q=0.9"); to overcome an error shown below:-

enter image description here

How can I overcome this problem?

Question 2:

In Firefox, the console reads:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/angproject/phpscript/register.php. (Reason: invalid token ‘application/x-www-form-urlencoded’ in CORS header ‘Access-Control-Allow-Headers’).

enter image description here

When checking in Chrome, I can see two calls in the network tab in console. The first call's request type is OPTIONS, while the second call has request type as POST.

In case of Firefox, I am getting only one call with request type OPTIONS. The POST request isn't taking place at all.

enter image description here

How can I fix this?


Solution

  • Having 2 requests is normal, due to CORS implementation (client on localhost:4200, server on localhost = different ports).

    https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

    Your problem is that you specified some CORS headers in your request

    headers.append("Access-Control-Allow-Methods","GET, POST");
    headers.append("Access-Control-Allow-Origin","*");
    

    These headers are meant to be added server side, you don't need to send them from angular. They are actually causing the problem.

    Remove them and it should work.

    More information about Access-Control-Allow-Headers

    Access-Control-Allow-Headers normally accepts a list of comma-separated headers. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers

    The wildcard (*) value that you have on your PHP code is not supported on all browsers. On Firefox, the wildcard is not implemented yet https://bugzilla.mozilla.org/show_bug.cgi?id=1309358

    That's why firefox is bloking your POST, since the preflight (OPTIONS) request kind of fails

    You don't need to specify Content-Type in your list since it's accepted by default

    The simple headers, Accept, Accept-Language, Content-Language, Content-Type (but only with a MIME type of its parsed value (ignoring parameters) of either application/x-www-form-urlencoded, multipart/form-data, or text/plain), are always available and don't need to be listed by this header.

    Edit: actually, you do need to specify content-type in the list of accepted headers since you are sending application/json, which is not in the above list