Community Documentation

Example: User login with API key using C#

Last updated September 15, 2008. Created by j.somers on September 15, 2008.
Log in to edit this page.

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);
  }
}

Comments

Drupal Struct

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

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

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

Windows Problem ...

Greetings ...

Thanks for the above example, it saved me plenty of hair pulling, expect that it did not work on Windows. Not sure if you tested on Windows. I found that the above code work fine on Linux.

On Windows I was gettings an error ...

Fault code: -32700
Fault string: Parse error. Request not well formed.

So, after much digging and even a few packet traces, I figured out that in Windows, the below line was the problem ...

nonce = "".join(random.sample(string.letters+string.digits, 10))

On Windows in would include a UTF-8 letters, which was causing a problem, so I limited it to ASCII characters only, with ...

nonce = "".join(random.sample(string.ascii_letters+string.digits, 10))

Not sure if there is a better way to do this, but I'm still just starting with this project and hope this will help somebody else.

Thanks
Lee

Where are cr.sessid and the

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

1

First of all, you should use

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

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 :(

Trying to do it for

Trying to do it for android... hard stuff ;)

Thanks for sharing it ! :)

-Pol-
Find me on Google+ or Twitter

Fom Android?

Have anyone an example of how to login and fetch a user-restricted page from an android application?

Invalid username or password

I always get invalid username or password message as result. API key, domain, OK, username and pw double checked.. Did it work for anyone? Does anyone have any ideas what do I do wrong?

Same problem

I've used the modified code from above, getting "[1] Wrong username or password."

Could this be the User Login action spawning a different session ID?

Also - Invalid username or password (Java client)

The message I get: Exception in thread "main" org.apache.xmlrpc.XmlRpcException: Wrong username or password.

Did someone managed to make a Java client work with Services (V. 2.2)?

This tutorial need to be

This tutorial need to be refined:

1. It requires CookComputing.XmlRpcV2 DLL library, or you will get a compiling error
2. The structs definition is inaccuracy as:
Drupal.sessionid should be sessid and DrupalUser.email should be mail. The first one will cause a missing non-optional parameters mapping exception and the second one will cause a null return value of user email.
3. The parameter of cr.sessid while invoking Login is also incorrect, it should be changed to cnct as this is the instant class of Drupal.

When you change these above code, it works fine. Good luck! If you have any more question regard to Drupal API with C# coding, you are welcome to contact me!

CCK fields

Nice tutorial,

However, where I can find information regarding how to use node.save to create a new node that have CCK fields?.
I try different alternatives, all of them without luck.

IDrupalServices drupal = XmlRpcProxyGen.Create();
XmlRpcStruct node = new XmlRpcStruct();
node["title"] = "New Node";
node["body"] = "Here's the new node body";
node["teaser"] = "Teaser view";
node["type"] = "content";
node["promote"] = false;
node["format"] = 2;
node["uid"] = 15;
node["name"] = "user";

(..)
XmlRpcStruct hdate = new XmlRpcStruct();
hdate.Add("value", "2010-01-21 20:00:00");
XmlRpcStruct odate = new XmlRpcStruct();
odate.Add("0", hdate);
node["field_date"] = odate;

(..)

drupal.NodeSave(
Session_id,
node);

Regards,

To un11imig : The structure

To un11imig :

The structure to save a CCK field is always the same, it's an array of maps. I dont' know the C# syntax because I did it in Java as said before, but I think you can adapt it from what I say... ?

So, for example if you have a custom CCK field named "mystuff", you will have to add a "field_mystuff" array to your node XmlRpcStruct.
This array contains as much item as you wish, each one represents a separate value for your CCK field : it's useful only for multi-valued fields, but even for a single-valued you need to provide a single-element array.

The elements of the array are "map" struct, so in you case it's probably an XmlRpcStruct : it contains each sub-field of your CCK field, and in most of the case I suppose it will be unique. The name of it depend on the type of CCK field you use. For example, for a numeric CCK type, the name is "value", and for a node-reference CCK type, the name is "nid", and so on...

The best to do for yo to discover the proper structures and names is to create manually a node with some CCK values filled, and to load it through the XML RPC node.get method, then explore it with your debugger... then you will be able to return the same kind of content to the node.save method !

I hope all this would help you...
Have fun ;)

New C#.NET API available

I've published an API for C# and .NET developers.
http://gizra.com/content/drutnet-drupal-net-api

Brice

How to map integer type?

I always get an error while trying to map uid in the DrupalUser struct, whether use 'public string uid' or 'public int uid', how could I?

Any idea how to stay logged

Any idea how to stay logged in using C#?
Issue: http://drupal.org/node/1099416

invalid api key

I created a vb version of this project (pushed up to https://sharpred@github.com/sharpred/ConsoleApplicationDrupal.git)

The first time I run it I get "Invalid API Key", subsequent runs return "token has been used previously for a request. Re-try with another nonce key". The second error is a bit spurious IMO as my code creates a new random nonce each time it is run.

I also noticed that the first run creates an entry in services_timestamp_nonce table that contains just the timestamp but no nonce or domain value. If I delete this record, it goes back to the "invalid api key" message.

The code works unauthenticated and the method call from /admin/build/services/browse/views.get on the site works also. Any ideas as to why this does not work?

I have an application written in Winform (dotNet) and I would like it to Upload File to Drupal 7.
Is it possible to do it with Drupal xmlRPC services ?
Can somebody help me please?

Hi. Can enybody show code for

Hi.
Can enybody show code for Drupal 7, and Services 3 explayn loging and create node? Ofcours in c#.

Drupal 7 Services 3 REST Server C Sharp Example

I think, this http://www.technogumbo.com/2012/05/Drupal-7-Services-3-REST-Server-C-Sha... can to help Drupal 7 Services 3 REST Server C Sharp Example

I think, this

I think, this http://www.technogumbo.com/2012/05/Drupal-7-Services-3-REST-Server-C-Sha... can to help Drupal 7 Services 3 REST Server C Sharp Example

About this page

Drupal version
Drupal 6.x

Develop for Drupal

Drupal’s online documentation is © 2000-2013 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License. Comments on documentation pages are used to improve content and then deleted.
nobody click here