2
votes

I am creating a simple cakephp backend. For the frontend I am using angular. Angular connects through an api route (using ADmad JWT plugin) to the backend and should log in to get a token.

My Api/UsersController.php

public function token()
{
    $user = $this->Auth->identify();
    if (!$user) {
        throw new UnauthorizedException('Invalid username or password');
    }
    $this->set([
        'success' => true,
        'data' => [
            'token' => JWT::encode(
                [
                    'sub' => $user['id'],
                    'exp' =>  time() + 604800
                ],
                Security::salt()
            )
        ],
        '_serialize' => ['success', 'data']
    ]);
}

Now when I want to test my connection with Postman, (with following options) it works:

Request-Type: POST
URL:          http://cake3api.app/api/users/token.json
Headers:      Accept       => application/vnd.api+json
              Content-Type => application/vnd.api+json
Body (JSON array):
{
    "username":"test",
    "password":"1234"
}

Answer:

 {
     "success": true,
     "data": {
         "token": "HERE_COMES_THE_TOKEN_AS_ANSWER"
      }
 }

Now I want to use this auth-api with Angular4. I created an AuthService, so I can make a promise to the cake-api:

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class AuthService {

    private BASE_URL: string = 'http://cake3api.app/api/users';
    private headers: Headers = new Headers({
        'Content-Type' : 'application/vnd.api+json',
        'Accept' : 'application/vnd.api+json'
    });

    constructor(private http: Http) { }

    login(user): Promise<any> {
        let url: string = `${this.BASE_URL}/token.json`;
        return this.http.post(url, user, {headers: this.headers}).toPromise();
    }
}

And I'm calling the service in my LoginComponent

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';

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

export class LoginComponent implements OnInit {

    constructor(private auth: AuthService) { }

    ngOnInit(): void {
        let sampleUser: any = {
            username: 'test' as string,
            password: '1234' as string
        };

        this.auth.login(sampleUser).then((user) => {
            console.log(user.json());
        }).catch((err) => {
            console.log(err);
        });
    }

}

With this, I receive following:

OPTIONS http://cake3api.app/api/users/token.json 401 (Unauthorized)

Failed to load http://cake3api.app/api/users/token.json: Response for preflight has invalid HTTP status code 401

// XHR Request & Response (from console)
**GENERAL**
Request URL:http://cake3api.app/api/users/token.json
Request Method:OPTIONS
Status Code:401 Unauthorized
Remote Address:127.0.0.1:80
Referrer Policy:no-referrer-when-downgrade

**RESPONSE HEADERS**
Access-Control-Allow-Headers:origin, x-requested-with, content-type
Access-Control-Allow-Methods:PUT, GET, POST, DELETE, OPTIONS
Access-Control-Allow-Origin:*
Connection:Keep-Alive
Content-Length:247
Content-Type:application/json; charset=UTF-8
Date:Fri, 27 Oct 2017 08:56:17 GMT
Keep-Alive:timeout=5, max=100
Server:Apache/2.4.27 (Win32) OpenSSL/1.0.2l PHP/7.1.9
X-DEBUGKIT-ID:ac7b0f38-4c97-44d4-8c26-9f95b94ce937
X-Powered-By:PHP/7.1.9

**REQUEST HEADERS**
Accept:*/*
Accept-Encoding:gzip, deflate
Accept-Language:de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,bs;q=0.2
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST
Cache-Control:no-cache
Connection:keep-alive
Host:cake3api.app
Origin:http://localhost:4200
Pragma:no-cache
Referer:http://localhost:4200/login
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36

Anyone got an idea, why this constellation is not working? My thoughts are, that angular is not sending the request as it should (wrong headers sent?). The weird thing is, that my tests with postman worked, but not with angular.

If someone thinks it's because cake does not accept the ORIGIN header, it should. I enabled the access-control in it's ./.htaccess

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"
    Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
    Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
</IfModule>

EDIT

Because the problem seems to be with the browser sending OPTIONS-request, I made a bypass, but still not working.

RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

The error message disappears, but a new one came:

Failed to load http://cake3api.app/api/users/token.json: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.

Now it says that the header Access-Control-Allow-Origin is not set. But I already set it in the same .htaccess file where I put the OPTIONS-request workaround. What a disturbing problem, already lost a lot of time for such small things...

2
Update: When I use chrome with disabled CORS, it works. chrome.exe --user-data-dir="C:/ChromeDevSession" --disable-web-security - Matt Backslash
If it says the header isn't present, chances are that this is true. Inspect your server response to figure whether the headers are present. Also check out github.com/snelg/cakephp-cors and github.com/ozee31/cakephp-cors for implementations of CORS OPTIONS requests handling on the CakePHP side. - ndm
Your server is misconfigured to require authentication for OPTIONS requests. The solution is to configure that server to not require authentication for OPTIONS requests. Because your browser is sending a CORS preflight OPTIONS request before trying the POST from your code; but when your browser sends that OPTIONS request, it (by design) doesn’t include any credentials in the request. - sideshowbarker

2 Answers

1
votes

Failed to load http://cake3api.app/api/users/token.json: Response for preflight has invalid HTTP status code 401

As you said postman is able to receive a correct response from the server while through the browser you get the above exception, this already explains what's the underlying issue.

The preflight request is an HTTP OPTION type request that is made by the browser before your actual request, the error means that you're not handling explicitely OPTION requests.

Make sure your php server accepts OPTION requests and it should work

EDIT: try to add the host instead of the wildcard * for the header Access-Control-Allow-Origin'

Header set Access-Control-Allow-Origin "http://localhost:4200"

and check the HTTP response headers in the chrome dev tools, make sure you have the right ones.

enter image description here

-1
votes

Add httpsercurity.cors in your configure method to avoid pre-flight.