Example: User login with API key using C#

Last modified: September 15, 2008 - 11:35

The following example will explain how to connect to your Drupal site using the Services module with the usage of an API key and login a user. I will not go in great depth but provide an overview and enough basic information to get you started.

Assumptions

The following assumptions are made:

  • A fully working Drupal 6 installation using Services 0.13 or above. (I have not tested it with previous releases, but it should be one containing the new API key handling.)
  • Enough C# experience so I don't need to tell you which using statements you require.

Prerequisites

Since the .NET framework does not ship with a default way to handle XML-RPC we will be using an external library which can be found at http://www.xml-rpc.net/. Download the ZIP file and add the correct DLL file as a resource to your project. Read the README file for more information about the version you should use.

Helper Functions

We require some helper functions to help us converting and computing the required hashed data.

// Get the current Unix time stamp.
public string GetUnixTimestamp()
{
  TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
  return Convert.ToString(Convert.ToUInt64(ts.TotalSeconds));
}

// Similar to the 'user_password' function Drupal uses.
public string GetNonce(int length)
{
  string allowedCharacters = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  StringBuilder password = new StringBuilder();
  Random rand = new Random();

  for (int i = 0; i < length; i++)
  {
    password.Append(allowedCharacters[rand.Next(0, (allowedCharacters.Length - 1))]);
  }
  return password.ToString();
}

// Compute the hash value.
public string GetHMAC(string message, string key)
{
  System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
  byte[] keyByte = encoding.GetBytes(key);
  byte[] messageByte = encoding.GetBytes(message);

  HMACSHA256 hmac = new HMACSHA256(keyByte);
  byte[] hashMessageByte = hmac.ComputeHash(messageByte);

  string sbinary = String.Empty;
  for (int i = 0; i < hashMessageByte.Length; i++)
  {
    // Converting to hex, but using lowercase 'x' to get lowercase characters
    sbinary += hashMessageByte[i].ToString("x2");
  }

  return sbinary;
}

We also need two structs, one containing a field for every field in the user table and one existing of a string which shall contain the session ID and the Drupal user struct.

// Since we don't want an error to pop up if the returned Drupal user object contains a field which
// does not map to a field or struct has we need to add the MappingOption.
[XmlRpcMissingMapping(MappingAction.Ignore)]
public struct DrupalUser
{
  // Fields
  public string name;
  public string email;
}

public struct Drupal
{
  public string sessionid;
  public DrupalUser user;
}

We also need an interface which contains the list of functions we want to access.

[XmlRpcUrl("http://example.com/services/xmlrpc")] // Change this if you are not using Clean URL's!
public interface IServiceSystem : IXmlRpcProxy
{
  [XmlRpcMethod("system.connect")]
  Drupal Connect();

  [XmlRpcMethod("user.login")]
  Drupal Login(string hash, string timestamp, string domain, string nonce, string sessid, string username, string password);
}

Main Program

static void Main(string[] args)
{
  try
  {
    IServiceSystem iss = XmlRpcProxyGen.Create<IServiceSystem>();
    Drupal cnct = iss.Connect();

    string timestamp = GetUnixTimestamp();
    string nonce = GetNonce(10);
    string domain = "example.com";
    string key = "your_api_key";
   
    StringBuilder sb = new StringBuilder();
    sb.Append(timestamp);
    sb.Append(";");
    sb.Append(domain);
    sb.Append(";");
    sb.Append(nonce);
    sb.Append(";");
    sb.Append("user.login");

    string hash = GetHMAC(sb.ToString(), key);
    Drupal lgn = iss.Login(hash, domain, timestamp, nonce, cr.sessid, "user", "password");

    Console.WriteLine("username: {0}", lgn.user.name);
    Console.WriteLine("timezone: {0}", lgn.user.timezone);
    Console.WriteLine("email: {0}", lgn.user.mail);
    Console.WriteLine("created: {0}", lgn.user.created);
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
}

Drupal Struct

Aaron.Moline - February 20, 2009 - 20:43

If you go to the "Services" -> "Drupal.Connect" and click the "call method" button, you will see the available object properties. In the example here, the "Drupal" struct should at least be:

public struct Drupal
{
     public string sessid;
     public DrupalUser user;
}

Thanks!

Python implementation

usonian - April 29, 2009 - 02:17

Thanks for this, I found it after much hair-pulling and it steered me in the right direction. Here's a Python implementation:

<?php
// using php tags only for whitespace formatting.

#!/bin/python

"""
Working example of connecting and authenticating to the Drupal services module,
(Adapted from the C# example at <a href="
http://drupal.org/node/308629" title="http://drupal.org/node/308629" rel="nofollow">http://drupal.org/node/308629</a>)

2009-04-28

andy
.chase@proofgroup.com
Works against Drupal 6.10
and Services 6.x-0.13
"""

import xmlrpclib, time, hmac, string, random, hashlib, pprint

#Create a new instance of the XML-RPC server we want to connect to:
drupal = xmlrpclib.ServerProxy("
http://server_hostname/services/xmlrpc")

#Call the services system.connect() method to establish a session:
session = drupal.system.connect()

#Set up items needed by the user.login() method:
timestamp = str(int(time.mktime(time.localtime())))
domain = "server_hostname"
#(Clever random string generation found in comments at <a href="http://ostas.blogspot.com/2006/12/python-generate-random-strings.html" title="http://ostas.blogspot.com/2006/12/python-generate-random-strings.html" rel="nofollow">http://ostas.blogspot.com/2006/12/python-generate-random-strings.html</a>)
nonce = "".join(random.sample(string.letters+string.digits, 10))

key = 'your_api_key'
user = 'your_username'
password = 'your_password'

hashMap = hmac.new(key, "%s;%s;%s;%s" % (timestamp, domain, nonce, "user.login"), hashlib.sha256)

try:
   
login = drupal.user.login(hashMap.hexdigest(), domain, timestamp, nonce, session['sessid'], user, password)
   
pprint.pprint(login)
except xmlrpclib.Fault, err:
    print
"A fault occurred"
   
print "Fault code: %d" % err.faultCode
   
print "Fault string: %s" % err.faultString
?>

(Sorry, the generic code formatter clobbers the whitespace in the try/except block - if you're copy/pasting this, be sure to add it back in.)

I wrote some classes for

guaka - May 2, 2009 - 21:46

I wrote some classes for Drupal Services in Python which I pushed into github.

Where are cr.sessid and the

ZDavid - June 22, 2009 - 18:29

Where are cr.sessid and the lgn.user.timezone, mail and created vairables declared?

1

First of all, you should use

eightbits - September 24, 2009 - 17:01

First of all, you should use name 'sessid', instead 'sessionid' for Drupal struct, and then use this sessid, that Connect function returns, in Login function. so this cr.sessid is your cnct.sessid.

user.timezone as you can see is not in DrupalUser. So it'll be an error ...

Java Implementation

lesioc - August 18, 2009 - 22:17

Here is my quick & dumb Java implementation, it needs Apache commons-codec and Apache xmlrpc-client libs to work.
I make it run with commons-codec-1.3 and xmlrpc-client-3.1.2

import java.net.URL;
import java.util.HashMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Hex;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

public class TestXmlRpcAccess {

// Method names, don't touch this
private static final String USER_LOGIN_METHOD = "user.login";
private static final String SYSTEM_CONNECT_METHOD = "system.connect";

// Configuration info : adapt with to your stuff...
private static final String USER_LOGIN = "login";
private static final String USER_PASSWORD = "password";
private static final String SERVER_URL = "http://yourdomain/services/xmlrpc";
private static final String API_KEY = "your_api_key";
private static final String API_DOMAIN = "your_api_domain";

public static void main(String[] args) throws Throwable {
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL(SERVER_URL));
XmlRpcClient client = new XmlRpcClient();
client.setConfig(config);
Object[] params = new Object[] {};
HashMap<?, ?> sess = (HashMap<?, ?>) client.execute(SYSTEM_CONNECT_METHOD, params);
int nowValue = (int) (System.currentTimeMillis() / 1000);
String now = "" + nowValue;
String nonce = now;
String passwd = USER_PASSWORD;
String message = now + ";" + API_DOMAIN + ";" + nonce + ";" + USER_LOGIN_METHOD;
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec ks = new SecretKeySpec(API_KEY.getBytes(), mac.getAlgorithm());
mac.init(ks);
String hash = new String(Hex.encodeHex((mac.doFinal(message.getBytes()))));
params = new Object[] { hash, API_DOMAIN, now, nonce, sess.get("sessid"), USER_LOGIN, passwd };
Object user = client.execute(USER_LOGIN_METHOD, params);
System.out.println(user);
}

}

Hope it helps someone ;)
Sorry for the bad looking indent, but pre doesn't work here :(

 
 

Drupal is a registered trademark of Dries Buytaert.