Posts Tagged ‘security’

HTTP authentication with sfGuard

Your application problably provides web services like a REST/SOAP API or RSS feeds. Such services are often publicly available. Nevertheless, you probably want to restrict access to these features, as the rest of the application.

Since web services are meant to be programatically callable, you cannot use the standard form/post way. One common solution is to to use HTTP authentication.

That said, it could be a good idea to use the same user restriction logic for that method.

About forms

In symfony 1.1 and above, the sfGuard authentification logic has moved to the bundled signin form.

One of the goal of the new form system, which is a full framework by itself, is to enable reuse of components.
It means that the new form object now embeds the request, validation and error handling.

To enable a HTTP authentication form, we simply need to customize the signin form presentation, and enable HTTP as symfony’s request parameters.

Example

This example assumes that you have a symfony application running with sfGuard installed and set up.

The first thing to do is to override the default signin action of the sfGuardAuth module in the application :

~apps/
 |~api/
 | |+config/
 | |+i18n/
 | |+lib/
 | `~modules/
 |   |+rss/
 |   `~sfGuardAuth/
 |     |~actions/
 |     | `-actions.class.php

In the signin methode, we set 401 status code to the response and add the WWW_Authenticate header, to enable HTTP authentification. Then, we simply map HTTP values to the request parameters expected by sfGuard. If user is validated, we redirect the user to the requested page :

class sfGuardAuthActions extends sfActions
{
  public function executeSignin($request)
  {
    $user = $this->getUser();

    if ($user->isAuthenticated())
    {
      return $this->redirect('@homepage');
    }

    $message = 'Authentification required';

    $this->form = new sfGuardFormSignin;

    if (isset($_SERVER['PHP_AUTH_USER']))
    {
      $request->setParameter('signin', array(
        'username' =>$_SERVER['PHP_AUTH_USER'],
        'password' =>$_SERVER['PHP_AUTH_PW'],
      ));

      $this->form->bind($request->getParameter('signin'));
      if ($this->form->isValid())
      {
        $values = $this->form->getValues();
        $this->getUser()->signin($values['user']);

        return $this->redirect($request->getUri());
      }
      else
      {
        $message = $this->form->getErrorSchema();
      }
    }

    $header_message = "Basic realm=\"$message\"";

    $this->getResponse()->setStatusCode(401);
    $this->getResponse()->setHttpHeader('WWW_Authenticate', $header_message);

    return sfView::NONE;
  }
}

You can see that we reuse sfGuard’s error messages in the HTTP header, to get the exact same authentification logic as a normal login form:

The same behavior can be observed using a HTTP request with parameters encoded directly into the URL:

$ curl -v "http://login:pass@apps.localhost/api/api_dev.php/index"
* About to connect() to apps.localhost port 80 (#0)
*   Trying 127.0.0.1... connected
* Connected to apps.localhost (127.0.0.1) port 80 (#0)
* Server auth using Basic with user 'as'
> GET /api/api_dev.php/index HTTP/1.1
> Authorization: Basic YXM6
> User-Agent: curl/7.18.2 (i386-apple-darwin8.11.1) libcurl/7.18.2 zlib/1.2.3
> Host: apps.localhost
> Accept: */*
>
< HTTP/1.1 401 Unauthorized

< Server: Apache/2.0.59 (Unix) PHP/5.2.5 DAV/2
< X-Powered-By: PHP/5.2.5
< Set-Cookie: symfony=2711f6b7440442a3746fc38771072ca7; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< Pragma: no-cache
* Authentication problem. Ignoring this.
< Www-Authenticate: Basic realm="password [Required.]
username [The username and/or password is invalid.]"
< Content-Length: 0
< Content-Type: text/html; charset=utf-8
<
* Connection #0 to host apps.localhost left intact
* Closing connection #0