miCRoSCoPiC_eaRthLinG
miCRoSCoPiC_eaRthLinG

Reputation: 2960

CakePHP 3.8 Authentication Plugin: FAILURE_CREDENTIALS_MISSING

This is my first attempt at using the Authentication plugin in Cake 3.8. I've followed the examples outlined in https://book.cakephp.org/authentication/1/en/index.html.

Angular / Typescript code for sending credentials to Cake:

  /**
   * Login
   */
  public login( username : string,
                password : string,
                captchaResponse : string ) {

    return this.http.post<any>( 'http://api.mydomain.localhost/users/token.json',
                                {
                                  username,
                                  password,
                                  captchaResponse
                                },
                                {
                                  headers : new HttpHeaders()
                                    .set( 'X-Requested-With', 'XMLHttpRequest' )
                                }
    ).pipe( map( response => {
      // Store user details and jwt token in local storage
      // to keep user logged in between page refreshes
      this.user = response.data;
      this.setUser( this.user.profile );
      this.setToken( this.user.token );
      return this.user;
    } ) );
  }

In Application.php

public function bootstrap() {
    // Authentication
    $this->addPlugin( 'Authentication' );
}

/**
 * Returns a service provider instance.
 *
 * @param \Psr\Http\Message\ServerRequestInterface $request Request
 * @param \Psr\Http\Message\ResponseInterface $response Response
 * @return \Authentication\AuthenticationServiceInterface
 */
public function getAuthenticationService( ServerRequestInterface $request, ResponseInterface $response ) {
    $service = new AuthenticationService();

    // Load identifiers
    $service->loadIdentifier( 'Authentication.Password', [
        'fields'   => [
            'username' => 'username',
            'password' => 'password',
        ],
        'resolver' => [
            'className' => 'Authentication.Orm',
            'finder'    => 'active',
            'userModel' => 'Users',
        ],
    ] );
    // Load the authenticators
    $service->loadAuthenticator( 'Authentication.Form', [
        'fields' => [
            'username' => 'username',
            'password' => 'password',
        ],
        'loginUrl' => '/users/token.json'
    ] );
    $service->loadIdentifier( 'Authentication.JwtSubject' );

    // Configure the service. (see below for more details)
    return $service;
}

public function middleware( $middlewareQueue ) {
    $middlewareQueue
        // Authentication Middleware
        ->add( new AuthenticationMiddleware( $this ) );

    return $middlewareQueue;
}

In AppController.php

// Authentication Component
$this->loadComponent( 'Authentication.Authentication' );

I'm sending the credentials to Cake (acting as a REST API) from an Angular 7 app. The creds are sent via a post request embedded in the body of the request. I keep getting authentication result as invalid, when I check via $result->isValid().

In UsersController.php, when I try to trace the error:

$this->log( $this->request->getData(), 'debug' );
$result = $result = $this->Authentication->getResult();
$this->log( $result, 'debug' );

I get the following output:

2019-12-03 07:35:30 Debug: Array
(
    [username] => myuser
    [password] => mypassword
    [captchaResponse] => 
)

2019-12-03 07:35:30 Debug: Authentication\Authenticator\Result Object
(
    [_status:protected] => FAILURE_CREDENTIALS_MISSING
    [_data:protected] => 
    [_errors:protected] => Array
        (
            [0] => Login credentials not found
        )

)

I just can't figure out why Cake is failing to detect the presence of the credentials in the post data. Anybody else faced the same issue and have a solution?

Thank you.

Upvotes: 0

Views: 1193

Answers (2)

ndm
ndm

Reputation: 60463

To elaborate on this beyond the possible solution already posted, when sending data in non form-data format, ie non-application/x-www-form-urlencoded, for example JSON (which AFAICT is the default for raw objects with Angular's HTTP client), you'll need to implement some kind of mechanism that decodes that data into a format that the involved code on the CakePHP side can read/understand, as by default PHP only parses form data.

With the old auth component it worked for you because you most likely are using the request handler component, which by default supports automatically decoding JSON request data into regular array style post data that can be retrieved from the request object ($request->getData()).

The new authentication plugin however runs authentication at middleware level, that is before any controllers (and therefore components) are involved, so the authentication middleware won't have access to the decoded data, the data on the request object will be empty (the raw JSON string will can be obtained via $request->input()).

In order to make this working, the body parser middleware has been introduced, it can do what the request handler component does, that is parse the raw input data, and populate the regular request data with it. You'd put it in the queue before your authentication middleware, disable the request handler components input decoding, and then it should work with JSON data just fine:

$middlewareQueue
    ->add(new \Cake\Http\Middleware\BodyParserMiddleware())
    ->add(new \Authentication\Middleware\AuthenticationMiddleware($this));
$this->loadComponent('RequestHandler', [
    'inputTypeMap' => [],
    // ...
]);

See also

Upvotes: 1

miCRoSCoPiC_eaRthLinG
miCRoSCoPiC_eaRthLinG

Reputation: 2960

Found the issue, finally. As Greg Schmidt suggested, adding some debug steps inside FormAuthenticator::_getData() helped resolve the issue. What I found is that _getData() was passing a null array to the authenticator.

If you look at the Angular code above, I was directly including the username and password as part of a dynamically created object in the body:

return this.http.post<any>( 'http://api.mydomain.localhost/users/token.json',
                            // NOT THE RIGHT WAY OF DOING IT
                            {
                              username,
                              password,
                              captchaResponse
                            },
                            {
                              headers : new HttpHeaders()
                                .set( 'X-Requested-With', 'XMLHttpRequest' )
                            }
)

For some reason, the new FormAuthenticator / Authetication plugin isn't able to parse this information - though this wasn't an issue with the old AuthComponent.

Instead, I had to modify the Angular code to utilize the FormData() object for automated addition of the Content-Type (application/x-www-form-urlencoded or formdata) header and the content boundary. The modified code is shown below:

// Prepare form data
const formData = new FormData();
formData.append( 'username', username );
formData.append( 'password', password );
formData.append( 'captcha', captchaResponse );

return this.http.post<any>( 'http://api.mydomain.localhost/users/token.json',
                            formData,
                            {
                              headers : new HttpHeaders()
                                .set( 'X-Requested-With', 'XMLHttpRequest' )
                                .set( 'Accept', 'application/json' )
                                .set( 'Cache-Control', 'no-cache' )
                            }
)

Hope this helps anyone facing the same issue in future.

Upvotes: 1

Related Questions