Example: User login with API key using C#

Last updated on
30 April 2025

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

Help improve this page

Page status: Not set

You can: