1

I'm trying to implement Basic Authentication in CakePHP with an application written entirely in jQuery. I was going to use OAuth2, but got fed up with the added complexity and seen as this application will be the only thing using the API, Basic Auth should be sufficient.

The login method looks like:

login: function(username, password) {    
    $.ajax({
        type: 'GET',
        url: 'http://api.domain.com/login',
        beforeSend : function(xhr) {
            var base64 = $.base64.encode(username + ':' + password);
            xhr.setRequestHeader("Authorization", "Basic " + base64);
        },
        dataType: 'jsonp',
        success: function(data) {
            if( data.response.status == 'error' ) {
                alert(data.response.message);
            } else {                
                // Save some sort of session                   
            }
        },
        error: function(a,b,c) {
            console.log(a,b,c);
        }
    });        
}

And on the CakePHP side:

public function login() {    
    if ($this->request->is('get')) {        
        if ($this->Auth->login()) {            
            $response = json_encode(
                array(
                    'meta'=>array(
                        'code'=>$this->response->statusCode(),
                        'in'=>round(microtime(true) - TIME_START, 4)
                    ),
                    'response'=>array(
                        'status'=>'success',
                        'message'=>'successfully logged in'
                    )
                )
            );                
        } else {   
            $response = json_encode(
                array(
                    'meta'=>array(
                        'code'=>$this->response->statusCode(),
                        'in'=>round(microtime(true) - TIME_START, 4)
                    ),
                    'response'=>array(
                        'status'=>'error',
                        'message'=>'incorrect login details'
                    )
                )
            );   
        }           
        // Handle JSONP
        if(isset($_GET['callback'])) {
            $response = $_GET['callback'] . '(' . $response . ')';
        }       
        // Return JSON
        $this->autoRender = false;
        $this->response->type('json');
        $this->response->body($response);           
    }       
}
  1. How do I save the user login details? As I can't create a session because the JavaScript MIGHT not be running on the same server (as it could be an external application, mobile app, etc.) The data WILL be being sent over SSL, but I'm not sure how I can remember user details, so the user doesn't have to keep logging in. As far as I know Basic Authentication is supposed to be stateless... so no cookies or sessions will even exist. Can anyone shed light on this?

Update: After doing some more research into this, I've found that the username and password needs sending with each request... so in effect there NEVER is a session or cookie. But I'd need to store this information somewhere? Otherwise the user would be logging in after every request on the page right?

  1. Once the user has logged in and is now authenticated... how can I make requests to data and handle the authentication to make sure the correct user has access, and if they create data that is created as them. As in OAuth2, I would have an access_token I could just pass to access data, but in Basic Auth, you don't have a token. And because there is no session, I can't access the logged in users information. So how do I do this?

  2. According to the CakePHP docs, you don't need a login method (for Basic Auth) in CakePHP 2.4+ so how exactly does the user login from the Client side and what controller method would I talk to when passing the username and password?

Update: When trying to access the login method based on my code above, I get the prompt for a username and password... but this is an allowed method! Any ideas why? As it prevents my JavaScript from sending the request as the endpoint can never be accessed. Also if I login using the prompt I remain logged in even if I visit the logout method or clear all my sessions/caches in the browser. So how do I delete a users login?

Any help to point me in the right direction would be much appreciated, as it feels I'm completely misunderstanding the way Basic Auth works... Some examples would be awesome!

Update 2:

According to the PHP Docs, the following is the way to check if a user is logged in or not and show a message if they cancel. The information is also stored without ANY USE of a session! So how does this same principle apply to CakePHP? For example, how do I show the correct 401 status and a custom message, how is the logged in user info stored in CakePHP if it's based on this?

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Text to send if user hits Cancel button';
    exit;
} else {
    echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
    echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
}
?>
bancer
  • 7,475
  • 7
  • 39
  • 58
Cameron
  • 27,963
  • 100
  • 281
  • 483

2 Answers2

3

Although Basic Authentication is stateless but according to CakePHP documentation:

Clients using Basic Authentication must support cookies. Since AuthComponent identifies users based on Session contents, clients using Basic Auth must support cookies.

http://api.cakephp.org/2.5/class-BasicAuthenticate.html

But:

Also you can set AuthComponent::$sessionKey to false to ensure AuthComponent doesn’t try to read user info from session. Stateless authentication will re-verify the user’s credentials on each request, this creates a small amount of additional overhead, but allows clients that to login in without using cookies.

http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#using-digest-and-basic-authentication-for-logging-in

Your AppController should be like this:

class AppController extends Controller {

    public $components = array('Auth', 'Session');

    public function beforeFilter() {
        $this->Auth->authorize = array('Controller');
        $this->Auth->authenticate = array('Basic');
        $this->Auth->sessionKey = false;
    }
}

You may also want to use $this->Auth->unauthorizedRedirect = false;. By default unauthorized user is redirected to the referrer URL or AuthComponent::$loginAction or ‘/’. If unauthorizedRedirect is set to false a ForbiddenException exception is thrown instead of redirecting.

And then in every controller you must have isAuthorized() method where you authorize users according to their roles the same way you would do with usual Form Authentication. F.ex.:

public function isAuthorized($user) {
   if (isset($user['role']) && ($user['role'] == 'admin')) {
     return true;
   }
   return false;
}

Because basic and digest authentication don’t require an initial POST or a form so if using only basic / digest authenticators you don’t require a login action in your controller.

http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#using-digest-and-basic-authentication-for-logging-in

That means you do not need to call Users::login() explicitly and when the user tries to access URL she will be presented Basic Authentication window where she must enter username and password every time the page is requested if you do not send Basic Authorization header like you do in your Ajax call xhr.setRequestHeader("Authorization", "Basic " + base64);.

If do not want that the user would be presented with such a window on every request you can store the user credentials in a cookie or preferably in the client's local storage (http://www.w3schools.com/html/html5_webstorage.asp) and set "Authorization" header on every request (to any action of any controller).

You can always send back the authenticated user details from your controller to the client this way:

$this->set(array(
     'user' => json_encode($this->Auth->user()),
     '_serialize' => array('user')
));

Probably you can do it from beforeFilter() so that the authenticated user details are send with each response.

bancer
  • 7,475
  • 7
  • 39
  • 58
  • The part "Clients using Basic Authentication must support cookies.." in API docs is lies. BasicAuthenticate now properly works in stateless mode. The docs will be fixed soon. – ADmad Jun 07 '14 at 13:05
1

Sessions can be used without cookies, share it as a parameter in the each query, and as the value of json response.

  1. Prepare your application to store sessionID in database or cache

  2. after successful ajax login, in the json response include the sessionID

  3. every next request must contain the sessionID as a parameter, the response must also include a sessionID

This is how I would do.

Salines
  • 5,674
  • 3
  • 25
  • 50
  • But Basic Auth doesn't use sessions right? It stores the auth details in the AUTH_USER and AUTH_PW variables and has no expiry time. – Cameron Jun 03 '14 at 17:21