Error message

Deprecated function: implode(): Passing glue string after array is deprecated. Swap the parameters in drupal_get_feeds() (line 394 of /home1/tylerfra/public_html/includes/common.inc).

Drupal JSON Services Examples with PhoneGap and JQuery Mobile for Android

Category: 

UPDATE: Please read this article for a much more detailed explanation on how to get PhoneGap up and running to communicate with Drupal Services.

UPDATE: There is security issue with the Services module prior to version 3.4. Now all autheticated service calls that use POST, PUT or DELETE need a CSRF token sent along in the request header. This article has NOT been udpated to show those changes! More Information and Workaround(s)

This info assumes you have Drupal 6.x, Services 3.x and PhoneGap/Eclipse/Android up and running. If you are new to Drupal Services, head over to the Drupal Services Examples article to get started. This page is a work in progress and I will continue to add more examples when days are 48 hours long.

System Connect, User Login, User Logout

assets/www/index.html

<!DOCTYPE html> 
<html>

  <head> 
    <title>Drupal JSON Services with PhoneGap and JQuery Mobile for Android</title> 
    <script type="text/javascript" charset="utf-8" src="phonegap-1.1.0.js"></script>
    <link rel="stylesheet" href="jquery.mobile-1.0a1.min.css" /> 
    <script src="jquery-1.4.3.min.js"></script>
    <script src="jquery.mobile-1.0a1.min.js"></script>
  </head>

<body>

<div data-role="page" id="tf_page_dashboard">
  <script type="text/javascript" charset="utf-8" src="scripts/dashboard.js"></script>

  <div data-role="header">
    <h1>Dashboard</h1>
  </div><!-- /header -->

  <div data-role="content">
    <p><a href="#tf_page_login" data-role="button" id="tf_login_button">Login</a></p>
    <p><a href="#" data-role="button" id="tf_logout_button">Logout</a></p>
  </div><!-- /content -->

</div><!-- /page -->

<div data-role="page" id="tf_page_login">
  <script type="text/javascript" charset="utf-8" src="scripts/login.js"></script>

  <div data-role="header">
    <h1>Login</h1>
  </div><!-- /header -->

  <div data-role="content" class='content'>
    <div>
      <label for="tf_page_login_name">Username</label>
      <input type="text" id="tf_page_login_name" />
    </div>
    <div>
      <label for="tf_page_login_pass">Password</label>
      <input type="password" id="tf_page_login_pass" />
    </div>
    <fieldset>
      <div><button type="button" data-theme="b" id="tf_page_login_submit">Login</button></div>
    </fieldset>
  </div><!-- /content -->

</div><!-- /page -->

</body>

</html>

assets/www/scripts/dashboard.js

$('#tf_page_dashboard').live('pageshow',function(){
  try {
    // BEGIN: drupal services system connect (warning: don't use https if you don't have ssl setup)
    $.ajax({
      url: "https://www.tylerfrankenstein.com/my_services_path/system/connect.json",
        type: 'post',
        dataType: 'json',
        error: function (XMLHttpRequest, textStatus, errorThrown) {
          alert('tf_page_login_submit - failed to login');
          console.log(JSON.stringify(XMLHttpRequest));
          console.log(JSON.stringify(textStatus));
          console.log(JSON.stringify(errorThrown));
        },
        success: function (data) {
          var tf_user = data.user;
          if (tf_user.uid == 0) { // user is not logged in, show the login button, hide the logout button
            $('#tf_login_button').show();
            $('#tf_logout_button').hide();
          }
          else { // user is logged in, hide the login button, show the logout button
            $('#tf_login_button').hide();
            $('#tf_logout_button').show();
          }
       }
    });
    // END: drupal services system connect
  }
  catch (error) { alert("tf_page_dashboard - " + error); }
});

$('#tf_logout_button').live("click",function(){
try {
  // BEGIN: drupal services user logout (warning: don't use https if you don't have ssl setup)
  $.ajax({
      url: "https://www.tylerfrankenstein.com/my_services_path/user/logout.json",
      type: 'post',
      dataType: 'json',
      error: function (XMLHttpRequest, textStatus, errorThrown) {
        alert('tf_logout_button - failed to logout');
        console.log(JSON.stringify(XMLHttpRequest));
        console.log(JSON.stringify(textStatus));
        console.log(JSON.stringify(errorThrown));
      },
      success: function (data) {
        alert("You have been logged out.");
        document.location.href = "../index.html#tf_page_dashboard";
      }
  });
  // END: drupal services system connect
}
catch (error) { alert("tf_logout_button - " + error); }
return false;
});

assets/www/scripts/login.js

$('#tf_page_login').live('pageshow',function(){
  try {
    // do some stuff when the page is ready, if you want...
  }
  catch (error) { alert("tf_page_login - " + error); }
});

$('#tf_page_login_submit').live('click',function(){
  
  var name = $('#tf_page_login_name').val();
  if (!name) { alert('Please enter your user name.'); return false; }
  
  var pass = $('#tf_page_login_pass').val();
  if (!pass) { alert('Please enter your password.'); return false; }
  
  // BEGIN: drupal services user login (warning: don't use https if you don't have ssl setup)
  $.ajax({
      url: "https://www.tylerfrankenstein.com/my_services_path/user/login.json",
      type: 'post',
      data: 'username=' + name + '&password=' + pass,
      dataType: 'json',
      error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert('tf_page_login_submit - failed to login');
        console.log(JSON.stringify(XMLHttpRequest));
        console.log(JSON.stringify(textStatus));
        console.log(JSON.stringify(errorThrown));
      },
      success: function (data) {
        document.location.href="../index.html#tf_page_dashboard";
      }
  });
  // END: drupal services user login
  return false;
});

Comments

Thank you for this tutorial! I was wondering if you could help me understand a few steps past this.

How could you store the returned json (sessionid, etc) to make authenticated posts with something like localstorage? I am sure I am missing something very obvious but I am just not seeing it. Any insight would be really appreciated, thanks!

tyler's picture

Thank you, I am glad it helped.

For example, in the System->Connect resource success call back function, you can do this:

success: function (data) {
  // This will show you what the json return object looks like as a string.
  // You can take the print out of this from eclipse and paste it into something 
  // like JSONLint to see a pretty print of this object
  console.log(JSON.stringify(data)); 

  // this saves it into local storage
  window.localStorage.setItem("my_system_connect_data_key",JSON.stringify(data));

  // this retrieves it from local storage
  var local_storage_data_retrieval = JSON.parse(window.localStorage.getItem("my_system_connect_data_key"));
}

Wow, what a quick reply! You sir are a gentleman and a scholar. I had gotten that far. What I don't understand is how to take elements out of that string for an authenticated request, such as creating a node. Unless I am misunderstanding how JSON.stringify works I have to get only the values for the keys I need.

So if I did something like:
$.ajax({
type: "POST",
url: 'URL/services/rest/node', //my endpoint
data: JSON.stringify(obj), //this is coming from a form....
dataType: 'json',
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('');
console.log(JSON.stringify(XMLHttpRequest));
console.log(JSON.stringify(textStatus));
console.log(JSON.stringify(errorThrown));
},
success: function(data){
}
});

where would I include the data from localstorage?

I really am not trying to force you to write my code for me. I'm just a sys admin literally being forced to write a mobile app (long story) and I've been running into walls all weekend. I sincerely appreciate any insight, thanks!

Thanks Tyler!Could you help me please?i want to send image from phonegap app to node of drupal how do i do that?sorry about my english

Thanks a lot. Was struggling with phonegap. But you help a lot.
Waiting for Druaplgap to become a downloadable module from Drupal.org

Perrrrfect!! Thanks
:)

I followed this (http://bit.ly/um1dg4) good and detailed tutorial but at the login I get an XMLHttpRequest Error
and couldn't figure out how to fix it or another way around to reach my data from the REST-Server.
Do everybody got it run from local by requesting the data on the drupal server?

I couldn't find any answer since three days.

I spent 3 days looking for a good tutorial on using jquery, json, drupal services but didn't find anything.
Then I found your post: http://tylerfrankenstein.com/code/android-app-with-drupal-7-services-pho... :)

I think this is the ONLY thorough post on this topic on entire internet. Million thanks for that post. :)

If I can take few minutes of yours for an advice...
I followed your post and setup a jquery mobile site. I am able to login on the site via JQM but can't retain the user session.
Right after login is successful, if I do system/connect.json, I only get uid: 0, annonymous user. Hence I can't do any furthur actions since JQM doesn't recognize user is already logged in.

I have also checked on my drupal site reports, it shows the user is logged in.

I thought you could throw some light on something I am missing here... appreciate your response. Thanks a lot once again for such a great post.

tyler's picture

It sounds like 'Session authentication' is not configured properly under your Service Endpoint's settings. Check out the troubleshooting section on this page for more info:

http://drupal.org/node/1603690

Basically, if no session authentication is setup, then all service resource calls are routed as an anonymous user.

Thanks Tyler for responding.
I have "Session Authentication" checkbox marked under EDIT page of Endpoint service.

My configuration is JQuery Mobile (lastest), Drupal Services 3.x, Drupal 6.x, SPYC (latest). I am not using DrupalGap or Phone Gap as of now.
I want to build a basic working mobile app as of now before wrapping it under PhoneGap framework, which I am planning to do later.

I also found out that session authentication isn't safe, so I switched to OAUTH authentication and right now struggling to make it work with above configuration. Do you happen to know about OAUTH setup for mobile app? If so, please guide.

Once again, many thanks for your wonderful post on jquery mobile app on drupal services. :)

tyler's picture

Sorry, I haven't used OAUTH at all yet so I can't shed much light on that. I have heard it is more secure than the 'Session authentication' option. I haven't had any problem with session authentication in terms of security (knock on wood). From my understanding, each resource call looks to the associated drupal permission defined in the service resource inc file, then asks Drupal wether or not the current user making the resource call has that permission. It seems to work very well, and I have tested it for many apps as anonymous/authenticated/admin users making sure nobody can be malicious. All is well so far. But I am no security expert...

I am also thinking about giving another try to "Session Authentication" tonight.
Anyways, setting up OAUTH is nightmare and many people are complaining about it all over the place :)

Your example is based on D7, have you also tried on D6? Did it work with services 3.x?

tyler's picture

I haven't tried the example specifically in Drupal 6. However, I have built a few mobile apps now for Drupal 6 sites running Services 3.x and the service calls are almost identical. There are a few differences I can think of in regards to the Node C.R.U.D. resources, mainly with how you declare the title and body of a node when making a call. But other than that, there are very few differences between Services for D6/D7.

I used services 6.3.x , 6.3.x-dev, 6.2.4x as well. In neither of them, the session is not maintained after login.

I am using login.json to do the login. I can see in drupal reports that user is successfully logged in. It also has session table updated appropriately. Right after login.json, in my jquery I trigger system/connect.json, it makes the call as anonymous only. If I try to logout, I get the message that "user is not logged in". :(

In one of the post I read, we need to set session id and session name in header:cookie. I tried that too, but browser (firefox, safari) both rejected the request with error message that "refused to potential unsafe header: cookie". :(

After all this, I reviewed the code of services 6.3.x and I noticed that in system/connect function, there' no logic to read from the session or cookies ( I may be wrong but see here ):

function _system_resource_connect() {
global $user;

$return = new stdClass();
$return->sessid = session_id();

services_remove_user_data($user);

$return->user = $user;

return $return;
}

Similarly in user/login, there's no code to read from existing session if user is already logged in.
function _user_resource_login($username, $password) {
global $user;

if ($user->uid) {
// user is already logged in
return services_error(t('Already logged in as !user.', array('!user' => $user->name)), 406);
}

user_authenticate(array('name' => $username, 'pass' => $password));

if (isset($user->uid) && $user->uid) {
// Regenerate the session ID to prevent against session fixation attacks.
sess_regenerate();

$return = new stdClass();
$return->sessid = session_id();
$return->session_name = session_name();

services_remove_user_data($user);

$return->user = $user;

watchdog('user', 'User set is %name; sessid=%sessid; sessname=%sessname',
array('%name'=> theme('placeholder', $user->name), '%sessid'=> theme('placeholder',$return->sessid),
'%sessname'=> theme('placeholder', $return->session_name)));
services_session_load($return->sessid);
return $return;
}
session_destroy();
return services_error(t('Wrong username or password.'), 401);
}

/**
* Logout the current user.
*/
function _user_resource_logout() {
global $user;

if (!$user->uid) {
// User is not logged in
return services_error(t('User is not logged in.'), 406);
}

watchdog('user', 'Session closed for %name.', array('%name' => theme('placeholder', $user->name)));

$null = NULL; // Only variables can be passed by reference workaround
user_module_invoke('logout', $null, $user );
// Destroy the current session.
session_destroy();

// Load the anonymous user
$user = drupal_anonymous_user();

return TRUE;
}

I am wondering how this is working at all. Do you see same code in your version of D6 services 3.x?
Here's also one post which says "session authentication" module is removed from services 3.x. http://drupal.org/node/1285428

had never been stuck on drupal issues for so long :(

tyler's picture

Sorry, I haven't had this issue before. I use the latest recommended version of Services 3.x (6.x-3.1 at the time of writing this) and have no problems. I'd recommend starting out simple and just trying to use the Firefox Poster plugin as an anonymous user and then make a call to your user login resource, once you get that working you should be all set to move forward. I still think it has something to do with the 'Session authentication' checkbox located at admin/build/services/list/my_endpoint_machine_name, I'd say flush all your caches (of course), then try removing your endpoint and starting one from scratch, then flushing the caches again. If that still doesn't work, then I am going to conclude that another module is interferring with Services.

Hi,i want to ask,i tried your code,when i logout,the logout button shows up instead of hidden.seems that the page was not refreshing.what kind of possibilities that makes that issues?thanks

tyler's picture

Be sure to pass along the CSRF token and you may have to use the jQueryMobile reloadPage option to have the page rebuilt.

I have pass along the CSRF token,do i have to pass the session too?The following code is bases on your tutorial <a href='http://tylerfrankenstein.com/code/android-app-with-drupal-7-services-pho...'> tutorial </a>  :

<a href='http://pastebin.com/S9eVuTir'>logout.js</a>

tyler's picture

The code looks fine, the CSRF token passing looks fine. I'm not sure about the session. Check out DrupalGap's implementation of Service calls to see how it is done there: https://github.com/signalpoint/DrupalGap/blob/7.x-1.x-alpha/modules/api/api.js

Hi,another thing,i also try to consume the services from php file,the codes are the same as yours example with extra csrf token,and i 'm using firefox console to check,when i logout,it return me this

<a href="http://s575.photobucket.com/user/yonghan79/media/ask.png.html" target="_blank"><img src="http://i575.photobucket.com/albums/ss197/yonghan79/ask.png" border="0" alt="ask photo ask.png"/></a>

Would you point me what makes the js/dashboard.js get params like js/dashboard.js? _=13xxxxx and is it possible that that one who makes the page won't refresh ?Thanks

tyler's picture

If you're on the dashboard page, and the login button is on the dashboard page, and you logout, and try to go back to the dashboard page, I don't think this is "possible". I'm having a similar bug with logout in DrupalGap, and haven't figured out a work around yet. Either way, I'd recommend using DrupalGap to build your mobile app, it has much more features than that tutorial and www.drupalgap.org has plenty of examples and is better supported. Good luck!

Thanks tyler,please teach me how to fix it when you found a  work around for it.God bless you. :)

I am an android /iphone developer. I am able to login and submit a node to a Services 3.3 system. But only text fields.  I am now trying to submit a Taxonomy field.  And not having any luck.   Any pointers or sugestions there?