You can make authenticated requests to REST Server using standard Drupal session-based security with no effort on your part. This is managed through session cookies. However when doing this from code (for instance when using drupal_http_request() or cURL), you may need to go through some extra work and set the cookie header by hand. The cookie header looks like this:

Cookie: SESS5913ad7ed2adf92cab1103dad2f5596c=213d28535c6972e16430a4e1e03ce7ea

This has three parts. The head identifier ('Cookie'), the session name ('SESS5913ad7ed2adf92cab1103dad2f5596c'), and the session id ('213d28535c6972e16430a4e1e03ce7ea'). Thankfully, when you call the user/login resource in Services, both the session name and session id are part of the response object, so it is pretty easy to put this header together and return it with the next request. Once you've done that, the next request will act under the authorized user's session.

Here is some sample code that demonstrates this. To set this up you will need a Drupal installation setup with an endpoint that has the user/login and node/retrieve resources enabled, and which does not give the anonymous user 'access content' restrictions.

Example: Drupal 7

Tested against 7.x-3.0-rc1.

<?php
$base_url = 'http://localhost/test_endpoint';
$data = array(
  'username' => 'admin',
  'password' => 'password',
);
$data = drupal_json_encode($data);
$options = array(
  'headers' => array(
    'Content-Type' => 'application/json',
  ),
  'method' => 'POST',
  'data' => $data
);

$response = drupal_http_request($base_url . '/user/login', $options);
$data = json_decode($response->data);

// Check if login was successful
if ($response->code == 200) {
  // Now recycle the login cookie we received in the first request
  $options['headers']['Cookie'] = $data->session_name . '=' . $data->sessid;
  
  // Get info about a user 
  $data = array();
  $options['data'] = http_build_query($data, '', '&');
  $options['method'] = 'GET';
  $response = drupal_http_request($base_url . '/user/32', $options);
}
?>

Parameters for drupal_http_request have changed in Drupal 7. Services 3 have different user login variables that need to be POSTed between each version of Drupal.

Example: Drupal 6

Tested against 6.x-3.0-beta2.

<?php
// This is the base URL for our installation
$base_url = 'http://domain.com/test_endpoint/';
// necessary or the response is empty:
$headers = array('Content-Type' => 'application/x-www-form-urlencoded');
// Login
$data = array(
  'username' => 'admin',
  'password' => 'password',
);
$data = http_build_query($data, '', '&');
$response = drupal_http_request($base_url . '/user/login', $headers, 'POST', $data);
$data = json_decode($response->data);

//$headers['Cookie'] = "$data->session_name=$data->sessid";
$response = drupal_http_request($base_url . '/node/21.json', $headers); // replace this with a node id on your system
print_r(json_decode($response->data));
?>

Note that the line which sets the cookie header is commented out. If you run this code you will get access denied when you try and access node 21 . Now uncomment the cookie header line and run again. It works! You can save the session name and session id somewhere convenient and use it for all future calls as needed.

This same method can be used in Drupal 7 to make authenticated calls via XMLRPC. In Drupal 6 this is not possible without this core patch.

Note that most cookies have expirations or may only be active for one session. You should respect these limitations and take them into account when building your system (although doing so is beyond the scope of this code sample.)

Comments

ngstigator’s picture

From http://plosquare.blogspot.com/2010/03/solution-for-post-empty-after.html

$headers = array('Content-Type' => 'application/x-www-form-urlencoded');
bsenftner’s picture

When using REST server, and having written a resource that implements Create, the Services 3.0 handling of POST data puts an additional wrapper around the data. For example, if you have a resource whose Create expects data like this:

    $data = array( 'id' => $nid, 'note' => $note, 'tags' => $tags );

Then the logic to POST that is:

    $headers = array();
    $headers['Content-Type'] = 'application/x-www-form-urlencoded';
    $headers['Cookie']       = $this->GetCookieHeader();  // obtained via a prior user/login
  
    $data = array( 'id' => $nid, 'note' => $note, 'tags' => $tags );
    $data = http_build_query(array('data'=>$data), '', '&');   // <--- NOTICE THIS ADDITIONAL WRAPPING

    $response = drupal_http_request('url-to-your-resource', $headers, 'POST', $data);
    // check if the POST was successful:
    if ($response->code == 200) {
      $data = json_decode($response->data);
      // do whatever you want with the success data
    }

In the http_build_query() call, you'll notice the POST data getting an additional array wrapper. Without that additional data wrapper, the response code from drupal_http_request() is 406, 'Not Acceptable'.

weri’s picture

The service module (user_resource.inc) and also the user form has the form names 'name' and 'pass' defined and not 'username' and 'password'.


$data = array(
  'name' => 'my user name',
  'pass' => 'my password',
);

When I change this names, the example above works perfect!

bsenftner’s picture

Which version of Services are you using?

In the Services 3.x-dev version, Feb 2011, file user_resource.inc: "login" is defined with the parameter names "username" and "password":

      'actions' => array(
        'login' => array(
          'help' => 'Login a user for a new session',
          'callback' => '_user_resource_login',
          'args' => array(
            array(
              'name' => 'username',
              'type' => 'string',
              'description' => 'A valid username',
              'source' => 'data',
              'optional' => FALSE,
            ),
            array(
              'name' => 'password',
              'type' => 'string',
              'description' => 'A valid password',
              'source' => 'data',
              'optional' => FALSE,
            ),
          ),
          'access callback' => 'services_access_menu',
          'file' => array('type' => 'inc', 'module' => 'services', 'name' => 'resources/user_resource'),
        ),
weri’s picture

I'm using 6.x-3.0-beta2 as described in the example and this version uses pass and name. It's also correct, that the dev/head revision use username and password.

I will update my code after upgrading to the next beta/rc version. :-)

Thanks,
Thomas

broncomania’s picture

Thx for sharing this working d6 example.I am working on this for hours and here is your solution!

MichaelCole’s picture

Hi, here are some php *client* scripts that don't use Drupal. This may be helpful if you are integrating with another system. Done with Services 7.x-3.x-rc3 using Quickstart 1.0.

Login.php

$service_url = 'http://example.dev/rest/user/login.xml'; // .xml asks for xml data in response
$post_data = array(
  'username' => 'admin',
  'password' => 'admin',
);
$post_data = http_build_query($post_data, '', '&'); // Format post data as application/x-www-form-urlencoded

// set up the request
$curl = curl_init($service_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // have curl_exec return a string

curl_setopt($curl, CURLOPT_POST, true);             // do a POST
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); // POST this data

// make the request
curl_setopt($curl, CURLOPT_VERBOSE, true); // output to command line
$response = curl_exec($curl);
curl_close($curl);
print "RESPONSE:\n";
var_dump($response);

// parse the response
$xml = new SimpleXMLElement($response);
$session_cookie = $xml->session_name .'='. $xml->sessid;
print "SESSION_COOKIE: $session_cookie";

file_put_contents('session_cookie.txt', $session_cookie);

authenticated_request.php

$service_url = 'http://example.dev/rest/user/1'; // .xml asks for xml data in response
$session_cookie = file_get_contents('session_cookie.txt');

// set up the request
$curl = curl_init($service_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // have curl_exec return a string

curl_setopt($curl, CURLOPT_COOKIE, "$session_cookie"); // use the previously saved session

// make the request
curl_setopt($curl, CURLOPT_VERBOSE, true); // output to command line
$response = curl_exec($curl);
curl_close($curl);
print "RESPONSE:\n";
var_dump($response);

logout.php

$service_url = 'http://example.dev/rest/user/logout'; 
$session_cookie = file_get_contents('session_cookie.txt');

// set up the request
$curl = curl_init($service_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // have curl_exec return a string

curl_setopt($curl, CURLOPT_COOKIE, "$session_cookie"); // use the previously saved session
curl_setopt($curl, CURLOPT_POST, true);             // do a POST
curl_setopt($curl, CURLOPT_POSTFIELDS, ""); // POST this data

// make the request
curl_setopt($curl, CURLOPT_VERBOSE, true); // output to command line
$response = curl_exec($curl);
curl_close($curl);
print "RESPONSE:\n";
var_dump($response);
giorgio79’s picture

Appreciated Michael Cole, yours was the first script that I was able to cut and paste and make it work right out of the box.

ajinkya.kulkarni’s picture

Thanks Michael Cole! Your is the only example I could get working right away.

To create a node with custom fields, we can use following code.

//----------------create a node -------------------------------

    $node_data = array(
        'type' => 'ct_metadata_core', //content type e.g. 'page' or 'article'
        'title' => 'test layer',
        'field_core_lat_n[und][0]' => array('value' => '90'),
        'field_core_lat_s[und][0]' => array('value' => '-90'),
        'field_core_long_e[und][0]' => array('value' => '180'),
        'field_core_long_w[und][0]' => array('value' => '-180'),
        'field_core_description[und][0]' => array('value' => 'National Data Buoy Center'),
        'field_core_originator[und][0]' => array('value' => 'NDBC'),
        'field_core_url[und][0]' => array('url' => 'http://www.ndbc.noaa.gov/kml/marineobs_as_kml.php?sort=pgm'),
        'field_cont_res_name_org[und][0]' => array('value' => 'test'),
           
    );


    $service_url = 'http://example.dev/rest/node'; // .xml asks for xml data in response
    $session_cookie = file_get_contents('session_cookie.txt');

    $node_data = http_build_query($node_data, '', '&'); // Format post data as application/x-www-form-urlencoded
    // set up the request
    $curl = curl_init($service_url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // have curl_exec return a string

    curl_setopt($curl, CURLOPT_COOKIE, "$session_cookie"); // use the previously saved session

    curl_setopt($curl, CURLOPT_POST, true);             // do a POST
    curl_setopt($curl, CURLOPT_POSTFIELDS, $node_data); // POST this data
    // make the request
    curl_setopt($curl, CURLOPT_VERBOSE, true); // output to command line
    $response = curl_exec($curl);
    curl_close($curl);
    print "CREATE NODE RESPONSE:\n";
    var_dump($response);
sarath.rajan’s picture

hi,

I am getting Access denied for user anonymous Error even if am passing the header cookie. Can any one please help me.

jatorresdev’s picture

I have the same problem sarath.rajan. any suggestions ?

tripper54’s picture

Thanks for the example, MichaelCole.

To get this working with the lastest version of Services I had to add a CSRF token request - token.php

$service_url = 'http://example.dev/rest/user/token.xml'; // .xml asks for xml data in response
$session_cookie = file_get_contents('session_cookie.txt');

$post_data = array();
$post_data = http_build_query($post_data, '', '&'); // Format post data as application/x-www-form-urlencoded

// set up the request
$curl = curl_init($service_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // have curl_exec return a string

curl_setopt($curl, CURLOPT_COOKIE, "$session_cookie"); // use the previously saved session
curl_setopt($curl, CURLOPT_POST, true);             // do a POST
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); // POST this data

// make the request
curl_setopt($curl, CURLOPT_VERBOSE, true); // output to command line
$response = curl_exec($curl);
curl_close($curl);
print "RESPONSE:\n";
var_dump($response);


// parse the response
$xml = new SimpleXMLElement($response);
$csrf = $xml->token;
print "TOKEN: $csrf";

file_put_contents('csrf_token.txt', $csrf);

You need to run this after logging in, then all subsequent requests need to fetch the CSRF token and add it as a header:

$csrf_token = file_get_contents('csrf_token.txt');
curl_setopt($curl, CURLOPT_HTTPHEADER,array(
  'X-CSRF-Token: ' . $csrf_token
));

------------------
Phil Dodd
twitter: @phildoddau

didlix’s picture

I am not sure why the cookie line is commented out in the drupal 6 exmaple, but this is my working code (getting user information, not node info):

<?php
	// This is the base URL for our installation
	$base_url = 'http://bakery.local/icon/';
	// necessary or the response is empty:
	$headers = array('Content-Type' => 'application/x-www-form-urlencoded');
	// Login
	$data = array(
	  'username' => 'rachel',
	  'password' => 'rachel',
	);
	$data = http_build_query($data, '', '&');
	$response = drupal_http_request($base_url . 'user/login', $headers, 'POST', $data);
	$data = json_decode($response->data);
	// uncommented and fixed cookie line so that the second request is using the same session
	$headers['Cookie'] = $data->session_name . '=' . $data->sessid;
	$response = drupal_http_request($base_url . 'user/1', $headers);
	print_r(json_decode($response->data));
?>
luisfn’s picture

I tried to use Michael Cole example, but it don't really worked for me... I just changed the $service_url and the $post_data values. If I try to access other resource like /rest/user/1 it worked fine. What can be wrong?

Apfel007’s picture

where to put the d6 example?
In a module or in a php- file in Drupal directory?

Do you need to kill the session or logout, after doing stuff with rest?

rodmaz’s picture

Apparently the login REST endpoint does not send the cookie anymore.
Just upgrade one of our servers to Drupal 7.17 and Services to 7.x-3.2 and suddenly the REST login endpoint does not send the cookie HTTP header.
Issue has been opened.

Musa.thomas’s picture

If you make a request on server.com and you are already login on it (server.com), is there any way to retrieve session or you must always ask to login.
For example I go on server.com, Im login, I go on client.com, I must login again on server.com?
(cause I try and global $user on server.com side is always anonymous).

Maruthachala Moorthy E’s picture

Were you ale to get the logged in session details on client.com. Am looking into a scenario where a user may login to drupal site and a client site on the same browser, and trying to post data onto drupal site thru services. Right now I am asking the user to enter their drupal credentials on client site even when they logged onto drupal site on the same browser.

yesjun’s picture

Uncomment the $cookie_domain variable in 'sites/default/settings.php'
and set it to your site's root:
$cookie_domain = 'mysite.com';

reference: http://yuriybabenko.com/blog/drupals-domain-access-and-sites-in-subfolders