diff --git services.install services.install
index f48725c..a72d3e8 100644
--- services.install
+++ services.install
@@ -10,65 +10,7 @@
  * Implementation of hook_schema().
  */
 function services_schema() {
-  $schema['services_keys'] = array(
-    'description' => 'Stores all Service keys.',
-    'fields' => array(
-      'kid' => array(
-        'description' => 'The service key ID.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => ''
-      ),
-      'title' => array(
-        'description' => 'The title of the service key.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => ''
-      ),
-      'domain' => array(
-        'description' => 'The domain of the service key.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => ''
-      ),
-    ),
-    'primary key' => array('kid')
-  );
-
-  $schema['services_timestamp_nonce'] = array(
-    'description' => 'Stores timestamp against nonce for repeat attacks.',
-    'fields' => array(
-      'timestamp' => array(
-        'description' => 'The timestamp used with the Nonce.',
-        'type'        => 'varchar',
-        'length'      => 32,
-        'not null'    => TRUE,
-        'default'     => ''
-      ),
-      'nonce' => array(
-        'description' => 'The random string used on the request.',
-        'type'        => 'varchar',
-        'length'      => 32,
-        'not null'    => TRUE,
-        'default'     => ''
-      ),
-      'domain' => array(
-        'description' => 'The domain that submitted the request.',
-        'type'        => 'varchar',
-        'length'      => 255,
-        'not null'    => TRUE,
-        'default'     => ''
-      ),
-    ),
-    'indexes' => array( 
-       'timestamp' => array('timestamp'), 
-    ), 
-    'primary key' => array('nonce'),    
-  );
-  return $schema;
+  return array();
 }
 
 /**
@@ -83,9 +25,19 @@ function services_install() {
  */
 function services_uninstall() {
   drupal_uninstall_schema('services');
+
+  // Drop legacy tables
+  $legacy_tables = array('services_keys', 'services_timestamp_nonce');
+  foreach ($legacy_tables as $table) {
+    if (db_table_exists($table)) {
+      db_drop_table($table);
+    }
+  }
+
   variable_del('services_use_key');
   variable_del('services_use_sessid');
   variable_del('services_debug');
+  variable_del('services_auth_module');
 }
 
 /**
diff --git services.module services.module
index 277109f..f5fb0df 100644
--- services.module
+++ services.module
@@ -69,46 +69,7 @@ function services_menu() {
     'type'              => MENU_LOCAL_TASK,
     'file'              => 'services_admin_browse.inc',
   );
-  $items['admin/build/services/keys'] = array(
-    'title'             => 'Keys',
-    'description'       => 'Manage application access to site services.',
-    'page callback'     => 'services_admin_keys_list',
-    'access callback'   => 'variable_get',
-    'access arguments'  => array('services_use_key', TRUE),
-    'type'              => MENU_LOCAL_TASK,
-    'file'              => 'services_admin_keys.inc',
-  );
-  $items['admin/build/services/keys/%'] = array(
-    'title'             => 'Edit key',
-    'access arguments'  => array('administer services'),
-    'page callback'     => 'drupal_get_form',
-    'page arguments'    => array('services_admin_keys_form'),
-    'file'              => 'services_admin_keys.inc',
-    'type'              => MENU_CALLBACK,
-  );
-  $items['admin/build/services/keys/%/delete'] = array(
-    'access arguments'  => array('administer services'),
-    'page callback'     => 'drupal_get_form',
-    'page arguments'    => array('services_admin_keys_delete_confirm', 4),
-    'file'              => 'services_admin_keys.inc',
-    'type'              => MENU_CALLBACK,
-  );
-  $items['admin/build/services/keys/list'] = array(
-    'title'             => 'List',
-    'type'              => MENU_DEFAULT_LOCAL_TASK,
-    'access callback'   => 'variable_get',
-    'access arguments'  => array('services_use_key', TRUE),
-    'weight'            => -10,
-  );
-  $items['admin/build/services/keys/add'] = array(
-    'title'             => 'Create key',
-    'page callback'     => 'drupal_get_form',
-    'page arguments'    => array('services_admin_keys_form'),
-    'access callback'   => 'variable_get',
-    'access arguments'  => array('services_use_key', TRUE),
-    'type'              => MENU_LOCAL_TASK,
-    'file'              => 'services_admin_keys.inc',
-  );
+
   $items['admin/build/services/settings'] = array(
     'title'             => 'Settings',
     'description'       => 'Configure service settings.',
@@ -127,6 +88,12 @@ function services_menu() {
     'type'              => MENU_DEFAULT_LOCAL_TASK,
     'weight'            => -10,
   );
+  $items['admin/services/ahah/security-options'] = array(
+    'file'              => 'services_admin_browse.inc',
+    'page callback'     => '_services_ahah_security_options',
+    'access arguments'  => array('administer services'),
+    'type'              => MENU_CALLBACK,
+  );
   $items['crossdomain.xml'] = array(
     'access callback'   => 'services_access_menu',
     'page callback'     => 'services_crossdomain_xml',
@@ -241,9 +208,79 @@ function services_error($message) {
 }
 
 /**
+ * Gets information about a authentication module.
+ * If a property name is passed the value of the property will be returned,
+ * otherwise the whole information array will be returned.
+ *
+ * @param string $property
+ *  Optional. The name of a single property to get. Defaults to null.
+ * @param string $module
+ *  Optional. The module to get info forDefaults to the current authentication module.
+ * @return mixed
+ *  The information array or property value, or FALSE if the information or property wasn't found
+ */
+function services_auth_info($property=NULL, $module=NULL) {
+  static $info = array();
+  // Default the module param to the current auth module
+  $module = $module ? $module : variable_get('services_auth_module', '');
+
+  if (!isset($info[$module])) {
+    if (!empty($module) && module_exists($module) && is_callable($module . '_authentication_info')) {
+      $info[$module] = call_user_func($module . '_authentication_info');
+    }
+    else {
+      $info[$module] = FALSE;
+    }
+  }
+
+  // If a property was requested it should be returned
+  if ($property) {
+    return isset($info[$module][$property]) ? $info[$module][$property] : FALSE;
+  }
+
+  // Return the info array
+  return $info[$module];
+}
+
+function services_auth_invoke($method, &$arg1=NULL, &$arg2=NULL) {
+  $module = variable_get('services_auth_module', '');
+  // Get information about the current auth module
+  $func = services_auth_info($method, $module);
+  if ($func) {
+    if ($file = services_auth_info('#file')) {
+      require_once(drupal_get_path('module', $module) . '/' . $file);
+    }
+
+    if (is_callable($func)) {
+      $args = func_get_args();
+      // Replace method name and arg1 with reference to $arg1 and $arg2.
+      array_splice($args, 0, 3, array(&$arg1, &$arg2));
+      return call_user_func_array($func, $args);
+    }
+  }
+}
+
+function services_auth_invoke_custom($module, $method, &$arg1=NULL, &$arg2=NULL) {
+  // Get information about the auth module
+  $func = services_auth_info($method, $module);
+  if ($func) {
+    if ($file = services_auth_info('#file', $module)) {
+      require_once(drupal_get_path('module', $module) . '/' . $file);
+    }
+
+    if (is_callable($func)) {
+      $args = func_get_args();
+      // Replace module and method name and arg1 with reference to $arg1 and $arg2.
+      array_splice($args, 0, 4, array(&$arg1, &$arg2));
+      return call_user_func_array($func, $args);
+    }
+  }
+}
+
+/**
  * This is the magic function through which all remote method calls must pass.
  */
-function services_method_call($method_name, $args = array()) {
+function services_method_call($method_name, $args = array(), $browsing=FALSE) {
   $method = services_method_get($method_name);
 
   // Check that method exists.
@@ -251,7 +288,7 @@ function services_method_call($method_name, $args = array()) {
     return services_error(t('Method %name does not exist.', array('%name' => $method_name)));
   }
 
-  // Check for missing args and identify if arg is required in the hash.
+  // Check for missing args
   $hash_parameters = array();
   foreach ($method['#args'] as $key => $arg) {
     if (!$arg['#optional']) {
@@ -259,70 +296,32 @@ function services_method_call($method_name, $args = array()) {
         return services_error(t('Missing required arguments.'));
       }
     }
-
-    // Key is part of the hash
-    if (isset($arg['#signed']) && $arg['#signed'] == TRUE && variable_get('services_use_key', TRUE)) {
-      if (is_numeric($args[$key]) || !empty($args[$key])) {
-        if (is_array($args[$key]) || is_object($args[$key])) {
-          $hash_parameters[] = serialize($args[$key]);
-        }
-        else{
-          $hash_parameters[] = $args[$key];
-        }
-      }
-      else{
-        $hash_parameters[] = '';
-      }
-    }
   }
 
-  if ($method['#key'] && variable_get('services_use_key', TRUE)) {
-    $hash = array_shift($args);
-    $domain = array_shift($args);
-    $timestamp = array_shift($args);
-    $nonce = array_shift($args);
-
-    $expiry_time = $timestamp + variable_get('services_key_expiry', 30);
-
-    if ($expiry_time < time()) {
-      return services_error(t('Token has expired.'));
+  // Check authentication
+  if ($auth_error = services_auth_invoke('authenticate_call', $method, $args)) {
+    if ($browsing) {
+      drupal_set_message(t('Authentication failed: !message', array('!message' => $auth_error)), 'error');
     }
-
-    // Still in time but has it been used before
-    if (db_result(db_query("SELECT count(*) FROM {services_timestamp_nonce}
-        WHERE domain = '%s' AND timestamp = %d AND nonce = '%s'",
-        $domain, $timestamp, $nonce))) {
-      return services_error(t('Token has been used previously for a request.'));
-    }
-    else{
-      db_query("INSERT INTO {services_timestamp_nonce} (domain, timestamp, nonce)
-        VALUES ('%s', %d, '%s')", $domain, $timestamp, $nonce);
-    }
-
-    $api_key = db_result(db_query("SELECT kid FROM {services_keys} WHERE domain = '%s'", $domain));
-
-    if (!services_validate_key($api_key, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash)) {
-      return services_error(t('Invalid API key.'));
+    else {
+      return services_error($auth_error);
     }
   }
 
-  // Add additonal processing for methods requiring authentication
-  $session_backup = NULL;
-  if ($method['#auth'] && variable_get('services_use_sessid', TRUE)) {
-    $sessid = array_shift($args);
-    if (empty($sessid)) {
-      return services_error(t('Invalid sessid.'));
-    }
-    $session_backup = services_session_load($sessid);
-  }
-
   // Load the proper file
   if ($file = $method['#file']) {
     module_load_include($file['file'], $file['module']);
   }
 
-  // Check access
-  $access_arguments = isset($method['#access arguments']) ? $method['#access arguments'] : $args;
+  // Construct access arguments array
+  if (isset($method['#access arguments'])) {
+    $access_arguments = $method['#access arguments'];
+  }
+  else {
+    // Just use the arguments array if no access arguments have been specified
+    $access_arguments = $args;
+  }
+
   // Call default or custom access callback
   if (call_user_func_array($method['#access callback'], $access_arguments) != TRUE) {
     return services_error(t('Access denied.'));
@@ -340,66 +339,41 @@ function services_method_call($method_name, $args = array()) {
     chdir($server_root);
   }
 
-  // Add additonal processing for methods requiring authentication.
-  if ($session_backup !== NULL) {
-    services_session_unload($session_backup);
+  return $result;
+}
+
+function services_get_all_resources() {
+  static $resource_cache;
+  if (!isset($resource_cache)) {
+    $resource_cache = module_invoke_all('service_resource');
+    foreach ($resource_cache as $name => &$res) {
+      $res['#name'] = $name;
+    }
   }
+  return $resource_cache;
+}
 
-  return $result;
+function services_delegate_access($perm) {
+  return services_auth_invoke('delegate_access', $perm);
 }
 
 /**
-   * This should probably be cached in drupal cache.
+ * Gets all service definitions
+ *
+ * @return array
+ *   An array containing all services and thir methods
  */
-function services_get_all() {
-  static $methods_cache;
-  if (!isset($methods_cache)) {
-    $methods = module_invoke_all('service');
+function services_get_all($include_resources=TRUE) {
+  $cache_key = 'services:methods';
 
-    // api_key arg
-    $arg_api_key = array(
-      '#name' => 'hash',
-      '#type' => 'string',
-      '#description' => t('A valid API key.'),
-    );
-
-    // sessid arg
-    $arg_sessid = array(
-      '#name' => 'sessid',
-      '#type' => 'string',
-      '#description' => t('A valid sessid.'),
-    );
-
-    // domain arg
-    $arg_domain_name = array(
-      '#name' => 'domain_name',
-      '#type' => 'string',
-      '#description' => t('A valid domain for the API key.'),
-    );
-
-    $arg_domain_time_stamp = array(
-      '#name' => 'domain_time_stamp',
-      '#type' => 'string',
-      '#description' => t('Time stamp used to hash key.'),
-    );
-
-    $arg_nonce = array(
-      '#name' => 'nonce',
-      '#type' => 'string',
-      '#description' => t('One time use nonce also used hash key.'),
-    );
+  if (($cache = cache_get($cache_key)) && isset($cache->data)) {
+    return $cache->data;
+  }
+  else {
+    $methods = module_invoke_all('service');
 
     foreach ($methods as $key => $method) {
 
-      // set method defaults
-      if (!isset($methods[$key]['#auth'])) {
-        $methods[$key]['#auth'] = TRUE;
-      }
-
-      if (!isset($methods[$key]['#key'])) {
-        $methods[$key]['#key'] = TRUE;
-      }
-
       if (!isset($methods[$key]['#access callback'])) {
         $methods[$key]['#access callback'] = 'services_access_menu';
       }
@@ -408,17 +382,6 @@ function services_get_all() {
         $methods[$key]['#args'] = array();
       }
 
-      if ($methods[$key]['#auth'] && variable_get('services_use_sessid', TRUE)) {
-        $methods[$key]['#args'] = array_merge(array($arg_sessid), $methods[$key]['#args']);
-      }
-
-      if ($methods[$key]['#key'] && variable_get('services_use_key', TRUE)) {
-        $methods[$key]['#args'] = array_merge(array($arg_nonce), $methods[$key]['#args']);
-        $methods[$key]['#args'] = array_merge(array($arg_domain_time_stamp), $methods[$key]['#args']);
-        $methods[$key]['#args'] = array_merge(array($arg_domain_name), $methods[$key]['#args']);
-        $methods[$key]['#args'] = array_merge(array($arg_api_key), $methods[$key]['#args']);
-      }
-
       // set defaults for args
       foreach ($methods[$key]['#args'] as $arg_key => $arg) {
         if (is_array($arg)) {
@@ -437,9 +400,13 @@ function services_get_all() {
       }
       reset($methods[$key]['#args']);
     }
-    $methods_cache = $methods;
+
+    // Allow auth module to alter the methods
+    services_auth_invoke('alter_methods', $methods);
+
+    cache_set($cache_key, $methods);
+    return $methods;
   }
-  return $methods_cache;
 }
 
 /**
@@ -463,24 +430,6 @@ function services_method_get($method_name) {
   return $method_cache[$method_name];
 }
 
-function services_validate_key($kid, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash) {
-  $hash_parameters = array_merge(array($timestamp, $domain, $nonce, $method_name), $hash_parameters);
-  $rehash = hash_hmac("sha256", implode(';', $hash_parameters), $kid);
-  return ($rehash == $hash) ? TRUE : FALSE;
-}
-
-function services_get_keys() {
-  static $keys;
-  if (!$keys) {
-    $keys = array();
-    $result = db_query("SELECT * FROM {services_keys}");
-    while ($key = db_fetch_object($result)) {
-      $keys[$key->kid] = $key;
-    }
-  }
-  return $keys;
-}
-
 /**
  * Make any changes we might want to make to node.
  */
diff --git services_admin_browse.inc services_admin_browse.inc
index bf1ce26..44fa0e4 100644
--- services_admin_browse.inc
+++ services_admin_browse.inc
@@ -98,9 +98,7 @@ function services_admin_browse_test() {
   $method = services_method_get(arg(4));
 
   $form['arg'] = array('#tree' => TRUE);
-  $timestamp = time();
-  $nonce = user_password();
-  
+
   foreach ($method['#args'] as $key => $arg) {
     $form['name'][$key]         = array(
       '#value' => $arg['#name']
@@ -109,62 +107,20 @@ function services_admin_browse_test() {
       '#value' => ($arg['#optional']) ? t('optional') : t('required')
     );
 
-    switch ($arg['#name']) {
-      case 'hash':
-        $form['arg'][$key] = array(
-          '#title'          => 'Hash',
-          '#type'           => 'textfield',
-          '#default_value'  => hash_hmac('sha256', $timestamp .';'. $_SERVER['HTTP_HOST'] .';'. $nonce .';'. arg(4), services_admin_browse_get_first_key())
-        );
-        break;
-
-      case 'sessid':
-        $form['arg'][$key] = array(
-          '#title'          => 'Session id',
-          '#type'           => 'textfield',
-          '#default_value'  => session_id()
-        );
-        break;
-
-      case 'domain_name':
-        $form['arg'][$key] = array(
-          '#title'          => 'Domain name',
-          '#type'           => 'textfield',
-          '#default_value'  => $_SERVER['HTTP_HOST']
-        );
-        break;
-
-      case 'domain_time_stamp':
-        $form['arg'][$key] = array(
-          '#title'          => 'Timestamp',
-          '#type'           => 'textfield',
-          '#default_value'  => $timestamp
-        );
-        break;
-
-      case 'nonce':
-        $form['arg'][$key] = array(
-          '#title'          => 'Nonce',
-          '#type'           => 'textfield',
-          '#default_value'  => $nonce
-        );
-        break;
-
-      default:
-        if (isset($arg['#size']) && $arg['#size'] == 'big') {
-          $form['arg'][$key] = array(
-            '#type'           => 'textarea'
-          );
-        }
-        else {
-          $form['arg'][$key] = array(
-            '#type'           => 'textfield'
-          );
-        }
-        break;
+    if (isset($arg['#size']) && $arg['#size'] == 'big') {
+      $form['arg'][$key] = array(
+        '#type'           => 'textarea'
+      );
+    }
+    else {
+      $form['arg'][$key] = array(
+        '#type'           => 'textfield'
+      );
     }
-
   }
+
+  services_auth_invoke('alter_browse_form', $form, $method);
+
   $form['submit'] = array(
     '#type'           => 'submit',
     '#value'          => t('Call method')
@@ -174,18 +130,11 @@ function services_admin_browse_test() {
   return $form;
 }
 
-function services_admin_browse_get_first_key() {
-  $keys = services_get_keys();
-  foreach ($keys as $kid => $key) {
-    return $kid;
-  }
-}
-
 function services_admin_browse_test_submit($form, $form_state) {
   global $_services_admin_browse_test_submit_result;
   $method = services_method_get(arg(4));
   $args = services_admin_browse_test_unserialize_args($form_state['values']['arg']);
-  $result = services_method_call($method['#method'], $args);
+  $result = services_method_call($method['#method'], $args, TRUE);
   $_services_admin_browse_test_submit_result = '<pre>'. htmlspecialchars(print_r($result, TRUE)) .'</pre>';
 }
 
@@ -257,50 +206,121 @@ function theme_services_admin_browse_test($form) {
  * Callback for admin page
  */
 function services_admin_settings() {
-  $node_types = node_get_types('names');
-  $defaults = isset($node_types['blog']) ? array('blog' => 1) : array();
-  $form['security'] = array(
-    '#title'        => t('Security'),
-    '#type'         => 'fieldset',
-    '#description'  => t('Changing security settings will require you to adjust all method calls. This will affect all applications using site services.'),
-  );
-  $form['security']['services_use_key'] = array(
-    '#type'           => 'checkbox',
-    '#title'          => t('Use keys'),
-    '#default_value'  => variable_get('services_use_key', TRUE),
-    '#description'    => t('When enabled all method calls need to provide a validation token to autheciate themselves with the server.'),
-  );
-  $form['security']['services_key_expiry'] = array(
-    '#type'           => 'textfield',
-    '#prefix'         => "<div id='services-key-expiry'>",
-    '#suffix'         => "</div>",
-    '#title'          => t('Token expiry time'),
-    '#default_value'  => variable_get('services_key_expiry', 30),
-    '#description'    => t('The time frame for which the token will be valid. Default is 30 secs'),
-  );
-  $form['security']['services_use_sessid'] = array(
-    '#type'           => 'checkbox',
-    '#title'          => t('Use sessid'),
-    '#default_value'  => variable_get('services_use_sessid', TRUE),
-    '#description'    => t('When enabled, all method calls must include a valid sessid. Only disable this setting if the application will use browser-based cookies.')
+  $auth_modules = module_implements('authentication_info');
+
+  // Add security options.
+  if (!empty($auth_modules)) {
+    $auth_options = array(''=>t('-- Select a authorization module'));
+    foreach ($auth_modules as $module) {
+      $info = services_auth_info(NULL, $module);
+      $auth_options[$info['#description']][$module] = $info['#title'];
+    }
+
+    $form['security'] = array(
+      '#title'        => t('Security'),
+      '#type'         => 'fieldset',
+      '#description'  => t('Changing security settings will require you to adjust all method calls. This will affect all applications using site services.'),
+    );
+
+    $form['security']['auth_module'] = array(
+      '#type' => 'select',
+      '#title' => t('Authorization module'),
+      '#options' => $auth_options,
+      '#required' => FALSE,
+      '#default_value' => variable_get('services_auth_module', ''),
+      '#ahah' => array(
+        'path' => 'admin/services/ahah/security-options',
+        'wrapper' => 'security-module-options',
+        'method' => 'replace',
+      ),
+    );
+
+    // Placeholder for the auth module options
+    // also used as wrapper for ahah.
+    $form['security']['options'] = array(
+      '#prefix' => '<div id="security-module-options">',
+      '#suffix' => '</div>',
+      'settings' => array(
+        '#value' => sprintf('<div class="description">%s</div>',
+          t('Select a authorization module to configure security')),
+      ),
+    );
+    // Get the configuration form for the authorization module
+    $settings = services_auth_invoke('security_settings');
+    if ($settings) {
+      $form['security']['options']['settings'] = $settings;
+    }
+  }
+  else { // Warn if no authorization module has been installed.
+    drupal_set_message(t('No authorization modules have been installed'), 'warning');
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save settings'),
   );
 
-  services_admin_js($form);
+  return $form;
+}
 
-  return system_settings_form($form);
+function services_admin_settings_validate($form, $form_state) {
+  // Invoke custom validation for the auth module
+  if (!empty($form_state['values']['auth_module'])) {
+    services_auth_invoke_custom($form_state['values']['auth_module'],
+      'security_settings_validate', $form_state);
+  }
+}
+
+function services_admin_settings_submit($form, $form_state) {
+  // Update the services oauth module variable *if needed*.
+  $old_auth = variable_get('services_auth_module', '');
+  $new_auth = $form_state['values']['auth_module'];
+  if ($old_auth != $new_auth) {
+    variable_set('services_auth_module', $new_auth);
+    // Rebuild menu so that security-related menu items can be conditionally created.
+    menu_rebuild();
+    drupal_set_message('Changed authentication method');
+  }
+  else {
+    drupal_set_message('Updated authentication settings');
+  }
+
+  // Allow the authorization module to handle submitted values.
+  services_auth_invoke('security_settings_submit', $form_state);
+
+  // Clear the services cache so that methods are updated according to auth settings
+  cache_clear_all('services:', 'cache', TRUE);
 }
 
 /**
- * UI enhancement for services page
+ * Callback for the security configuration form ahah.
  */
-function services_admin_js($form) {
-  $out = <<<EOJS
-  $(document).ready(function() {
-    $("#services-key-expiry")[$("#edit-services-use-key").attr('checked') ? 'show' : 'hide']();
-    $("#edit-services-use-key").click(function() {
-      $("#services-key-expiry")[$(this).attr('checked') ? 'show' : 'hide']();
-   });
-  });
-EOJS;
-  drupal_add_js($out, 'inline', 'footer');
-}
+function _services_ahah_security_options() {
+  $cached_form_state = array();
+  $cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state);
+
+  if (!empty($_POST['auth_module'])) {
+    $settings = services_auth_invoke_custom($_POST['auth_module'], 'security_settings');
+  }
+
+  if ($settings) {
+    $cached_form['security']['options']['settings'] = $settings;
+  }
+  else {
+    unset($cached_form['security']['options']['settings']);
+  }
+
+  form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state);
+
+  $form_state = array('submitted' => FALSE);
+  $options = $cached_form['security']['options'];
+  unset($options['#prefix'], $options['#suffix']);
+  $options = form_builder('_services_ahah_security_options', $options, $form_state);
+  $output = drupal_render($options);
+
+  print drupal_to_js(array(
+    'status' => TRUE,
+    'data' => $output,
+  ));
+  exit;
+}
\ No newline at end of file
diff --git services_admin_keys.inc services_admin_keys.inc
deleted file mode 100644
index 282c60e..0000000
--- services_admin_keys.inc
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-// $Id: services_admin_keys.inc,v 1.3.2.15 2009/01/12 05:55:05 marcingy Exp $
-/**
- * @author Services Dev Team
- * @file
- *  Generate security keys.
- */
-
-function services_admin_keys_list() {
-  $keys = services_get_keys();
-  $header = array(t('Key'), t('Title'), t('Domain'), array('data' => t('Operations'), 'colspan' => '2'));
-  $rows = array();
-
-  foreach ($keys as $kid => $key) {
-    $row = array();
-
-    $row[] = $kid;
-    $row[] = $key->title;
-    $row[] = $key->domain;
-
-    // Populate the operations field.
-    $operations = array();
-
-    // Set the edit column.
-    $operations[] = array('data' => l(t('edit'), 'admin/build/services/keys/'. $kid));
-
-    // Set the delete column.
-    $operations[] = array('data' => l(t('delete'), 'admin/build/services/keys/'. $kid .'/delete'));
-
-    foreach ($operations as $operation) {
-      $row[] = $operation;
-    }
-    $rows[] = $row;
-  }
-
-  if (empty($rows)) {
-    $rows[] = array(array('data' => t('No API keys created.'), 'colspan' => '5', 'class' => 'message'));
-  }
-
-  return theme('table', $header, $rows);
-}
-
-function services_admin_keys_form() {
-  $kid = arg(4);
-
-  $key = db_fetch_object(db_query("SELECT * FROM {services_keys} WHERE kid = '%s'", $kid));
-
-  $key_kid = isset($key->kid) ? $key->kid : '';
-  $key_title = isset($key->title) ? $key->title : '';
-  $form['kid'] = array(
-    '#type'           => 'hidden',
-    '#default_value'  => $key_kid,
-  );
-
-  if ($key_kid != '') {
-    $form['key'] = array(
-      '#type'           => 'markup',
-      '#title'          => t('Key'),
-      '#value'          => '<strong>'. t('API Key') .':</strong> '. $key_kid,
-    );
-  }
-
-  $form['title'] = array(
-    '#title'          => t('Application title'),
-    '#type'           => 'textfield',
-    '#default_value'  => $key_title,
-    '#description'    => t('The title of the application or website using the service.'),
-  );
-  $form['domain'] = array(
-    '#title'          => t('Allowed domain'),
-    '#type'           => 'textfield',
-    '#default_value'  => isset($key->domain) ? $key->domain : '',
-    '#description'    => t('External domain allowed to use this key.'),
-  );
-
-  $form['submit'] = array(
-    '#type'           => 'submit',
-    '#value'          => $key_title != '' ? t('Save key') : t('Create key'),
-  );
-
-  return $form;
-}
-
-function services_admin_keys_form_submit($form, &$form_state) {
-  services_admin_keys_save($form_state['values']);
-
-  $form_state['redirect']  =  'admin/build/services/keys';
-}
-
-function services_admin_keys_save(&$key) {
-  $is_existing = FALSE;
-  $key['kid'] = !empty($key['kid']) ? $key['kid'] : md5(uniqid(mt_rand(), TRUE));
-  $is_existing =  db_result(db_query("SELECT count(*) FROM {services_keys}
-    WHERE kid = '%s'", $key['kid']));
-
-  if ($is_existing) {
-    db_query("UPDATE {services_keys} SET title = '%s', domain = '%s'
-      WHERE kid = '%s'", $key['title'], $key['domain'], $key['kid']);
-    return SAVED_UPDATED;
-  }
-  else {
-    db_query("INSERT INTO {services_keys} (kid, title, domain)
-      VALUES ('%s', '%s', '%s')", $key['kid'], $key['title'], $key['domain']);
-    return SAVED_NEW;
-  }
-}
-
-function services_admin_keys_delete($kid) {
-  db_query("DELETE FROM {services_keys} WHERE kid = '%s'", $kid);
-}
-
-function services_admin_keys_delete_confirm(&$form_state, $kid = 0) {
-  $key = db_fetch_object(db_query("SELECT * FROM {services_keys} WHERE kid = '%s'", $kid));
-
-  $form['kid'] = array('#type' => 'value', '#value' => $key->kid);
-
-  $message = t('Are you sure you want to delete the key %key?', array('%key' => $key->kid));
-  $caption = '';
-
-  $caption .= '<p>'. t('This action cannot be undone.') .'</p>';
-
-  return confirm_form($form, $message, 'admin/build/services/keys', $caption, t('Delete'));
-}
-
-function services_admin_keys_delete_confirm_submit($form, &$form_state) {
-  services_admin_keys_delete($form_state['values']['kid']);
-
-  $t_args = array('%key' => $form_state['kid']);
-  drupal_set_message(t('The key %key has been deleted.', $t_args));
-  watchdog('menu', 'Deleted key %key.', array('%key' => $t_args), WATCHDOG_NOTICE);
-
-  $form_state['redirect'] = 'admin/build/services/keys';
-}
diff --git services_keyauth.admin.inc services_keyauth.admin.inc
new file mode 100644
index 0000000..cbfd823
--- /dev/null
+++ services_keyauth.admin.inc
@@ -0,0 +1,133 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ *  Generate security keys.
+ */
+
+function services_keyauth_admin_keys_list() {
+  $keys = services_keyauth_get_keys();
+  $header = array(t('Key'), t('Title'), t('Domain'), array('data' => t('Operations'), 'colspan' => '2'));
+  $rows = array();
+
+  foreach ($keys as $kid => $key) {
+    $row = array();
+
+    $row[] = $kid;
+    $row[] = $key->title;
+    $row[] = $key->domain;
+
+    // Populate the operations field.
+    $operations = array();
+
+    // Set the edit column.
+    $operations[] = array('data' => l(t('edit'), 'admin/build/services/keys/'. $kid));
+
+    // Set the delete column.
+    $operations[] = array('data' => l(t('delete'), 'admin/build/services/keys/'. $kid .'/delete'));
+
+    foreach ($operations as $operation) {
+      $row[] = $operation;
+    }
+    $rows[] = $row;
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No API keys created.'), 'colspan' => '5', 'class' => 'message'));
+  }
+
+  return theme('table', $header, $rows);
+}
+
+function services_keyauth_admin_keys_form() {
+  $kid = arg(4);
+
+  $key = db_fetch_object(db_query("SELECT * FROM {services_keys} WHERE kid = '%s'", $kid));
+
+  $key_kid = isset($key->kid) ? $key->kid : '';
+  $key_title = isset($key->title) ? $key->title : '';
+  $form['kid'] = array(
+    '#type'           => 'hidden',
+    '#default_value'  => $key_kid,
+  );
+
+  if ($key_kid != '') {
+    $form['key'] = array(
+      '#type'           => 'markup',
+      '#title'          => t('Key'),
+      '#value'          => '<strong>'. t('API Key') .':</strong> '. $key_kid,
+    );
+  }
+
+  $form['title'] = array(
+    '#title'          => t('Application title'),
+    '#type'           => 'textfield',
+    '#default_value'  => $key_title,
+    '#description'    => t('The title of the application or website using the service.'),
+  );
+  $form['domain'] = array(
+    '#title'          => t('Allowed domain'),
+    '#type'           => 'textfield',
+    '#default_value'  => isset($key->domain) ? $key->domain : '',
+    '#description'    => t('External domain allowed to use this key.'),
+  );
+
+  $form['submit'] = array(
+    '#type'           => 'submit',
+    '#value'          => $key_title != '' ? t('Save key') : t('Create key'),
+  );
+
+  return $form;
+}
+
+function services_keyauth_admin_keys_form_submit($form, &$form_state) {
+  services_keyauth_admin_keys_save($form_state['values']);
+
+  $form_state['redirect']  =  'admin/build/services/keys';
+}
+
+function services_keyauth_admin_keys_save(&$key) {
+  $is_existing = FALSE;
+  $key['kid'] = !empty($key['kid']) ? $key['kid'] : md5(uniqid(mt_rand(), TRUE));
+  $is_existing =  db_result(db_query("SELECT count(*) FROM {services_keys}
+    WHERE kid = '%s'", $key['kid']));
+
+  if ($is_existing) {
+    db_query("UPDATE {services_keys} SET title = '%s', domain = '%s'
+      WHERE kid = '%s'", $key['title'], $key['domain'], $key['kid']);
+    return SAVED_UPDATED;
+  }
+  else {
+    db_query("INSERT INTO {services_keys} (kid, title, domain)
+      VALUES ('%s', '%s', '%s')", $key['kid'], $key['title'], $key['domain']);
+    return SAVED_NEW;
+  }
+}
+
+function services_keyauth_admin_keys_delete($kid) {
+  db_query("DELETE FROM {services_keys} WHERE kid = '%s'", $kid);
+}
+
+function services_keyauth_admin_keys_delete_confirm(&$form_state, $kid = 0) {
+  $key = db_fetch_object(db_query("SELECT * FROM {services_keys} WHERE kid = '%s'", $kid));
+
+  $form['kid'] = array('#type' => 'value', '#value' => $key->kid);
+
+  $message = t('Are you sure you want to delete the key %key?', array('%key' => $key->kid));
+  $caption = '';
+
+  $caption .= '<p>'. t('This action cannot be undone.') .'</p>';
+
+  return confirm_form($form, $message, 'admin/build/services/keys', $caption, t('Delete'));
+}
+
+function services_keyauth_admin_keys_delete_confirm_submit($form, &$form_state) {
+  services_keyauth_admin_keys_delete($form_state['values']['kid']);
+
+  $t_args = array('%key' => $form_state['kid']);
+  drupal_set_message(t('The key %key has been deleted.', $t_args));
+  watchdog('menu', 'Deleted key %key.', array('%key' => $t_args), WATCHDOG_NOTICE);
+
+  $form_state['redirect'] = 'admin/build/services/keys';
+}
diff --git services_keyauth.inc services_keyauth.inc
new file mode 100644
index 0000000..8a7dc63
--- /dev/null
+++ services_keyauth.inc
@@ -0,0 +1,201 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ *  The implementation of the key authentication scheme
+ */
+
+function _services_keyauth_security_settings() {
+  $form['services_use_key'] = array(
+    '#type'           => 'checkbox',
+    '#title'          => t('Use keys'),
+    '#default_value'  => variable_get('services_use_key', TRUE),
+    '#description'    => t('When enabled all method calls need to provide a validation token to autheciate themselves with the server.'),
+  );
+  $form['services_key_expiry'] = array(
+    '#type'           => 'textfield',
+    '#prefix'         => "<div id='services-key-expiry'>",
+    '#suffix'         => "</div>",
+    '#title'          => t('Token expiry time'),
+    '#default_value'  => variable_get('services_key_expiry', 30),
+    '#description'    => t('The time frame for which the token will be valid. Default is 30 secs'),
+  );
+  $form['services_use_sessid'] = array(
+    '#type'           => 'checkbox',
+    '#title'          => t('Use sessid'),
+    '#default_value'  => variable_get('services_use_sessid', TRUE),
+    '#description'    => t('When enabled, all method calls must include a valid sessid. Only disable this setting if the application will user browser-based cookies.')
+  );
+  return $form;
+}
+
+function _services_keyauth_security_settings_validate($form_state) {
+  if (!preg_match('/^\d+$/', $form_state['values']['services_key_expiry'])) {
+    form_set_error('services_key_expiry', t('The token expiry time must specified in whole seconds as a number'));
+  }
+}
+
+function _services_keyauth_security_settings_submit($form_state) {
+  // Store all values from "our" form as variables.
+  foreach (_services_keyauth_security_settings() as $key => $field) {
+    variable_set($key, $form_state['values'][$key]);
+  }
+}
+
+function _services_keyauth_alter_methods(&$methods) {
+  // Skip this if no services have been activated
+  if (!is_array($methods) || empty($methods)) {
+    return;
+  }
+
+  // sessid arg
+  $arg_sessid = array(
+    '#name' => 'sessid',
+    '#type' => 'string',
+    '#description' => t('A valid sessid.'),
+  );
+
+  $arg_domain_time_stamp = array(
+    '#name' => 'domain_time_stamp',
+    '#type' => 'string',
+    '#description' => t('Time stamp used to hash key.'),
+  );
+
+  $arg_nonce = array(
+    '#name' => 'nonce',
+    '#type' => 'string',
+    '#description' => t('One time use nonce also used hash key.'),
+  );
+
+  // domain arg
+  $arg_domain_name = array(
+    '#name' => 'domain_name',
+    '#type' => 'string',
+    '#description' => t('A valid domain for the API key.'),
+  );
+
+  // api_key arg
+  $arg_api_key = array(
+    '#name' => 'hash',
+    '#type' => 'string',
+    '#description' => t('A valid API key.'),
+  );
+
+  foreach ($methods as $key => &$method) {
+    // set method defaults
+    if (!isset($method[$key]['#auth'])) {
+      $method['#auth'] = TRUE;
+    }
+
+    if (!isset($method[$key]['#key'])) {
+      $method['#key'] = TRUE;
+    }
+
+    if ($method['#auth'] and variable_get('services_use_sessid', TRUE)) {
+       array_unshift($method['#args'], $arg_sessid);
+    }
+
+    if ($method['#key'] and variable_get('services_use_key', TRUE)) {
+      array_unshift($method['#args'], $arg_nonce);
+      array_unshift($method['#args'], $arg_domain_time_stamp);
+      array_unshift($method['#args'], $arg_domain_name);
+      array_unshift($method['#args'], $arg_api_key);
+    }
+  }
+}
+
+function _services_keyauth_get_first_key() {
+  $keys = services_keyauth_get_keys();
+  foreach ($keys as $kid => $key) {
+    return $kid;
+  }
+}
+
+function _services_keyauth_alter_browse_form(&$form, $method) {
+  $timestamp = time();
+  $nonce = user_password();
+
+  foreach ($method['#args'] as $key => $arg) {
+    switch ($arg['#name']) {
+      case 'hash':
+        $form['arg'][$key]['#default_value'] = hash_hmac('sha256',
+          $timestamp .';'. $_SERVER['HTTP_HOST'] .';'. $nonce .';'. arg(4),
+          _services_keyauth_get_first_key()
+        );
+        break;
+      case 'sessid':
+        $form['arg'][$key]['#default_value']  = session_id();
+        break;
+      case 'domain_name':
+        $form['arg'][$key]['#default_value'] = $_SERVER['HTTP_HOST'];
+        break;
+      case 'domain_time_stamp':
+        $form['arg'][$key]['#default_value'] = $timestamp;
+        break;
+      case 'nonce':
+        $form['arg'][$key]['#default_value'] = $nonce;
+        break;
+    }
+  }
+}
+
+function _services_keyauth_authenticate_call($method, $args) {
+  // Get parameters that are used for hash
+  $hash_parameters = array();
+  foreach ($method['#args'] as $key => $arg) {
+    if (isset($arg['#signed']) && $arg['#signed'] == TRUE && variable_get('services_use_key', TRUE)) {
+      if (is_numeric($args[$key]) || !empty($args[$key])) {
+        if (is_array($args[$key]) || is_object($args[$key])) {
+          $hash_parameters[] = serialize($args[$key]);
+        }
+        else{
+          $hash_parameters[] = $args[$key];
+        }
+      }
+      else{
+        $hash_parameters[] = '';
+      }
+    }
+  }
+
+  if ($method['#key'] and variable_get('services_use_key', TRUE)) {
+    $hash = array_shift($args);
+    $domain = array_shift($args);
+    $timestamp = array_shift($args);
+    $nonce = array_shift($args);
+
+    $expiry_time = $timestamp + variable_get('services_key_expiry', 30);
+
+    if ($expiry_time < time()) {
+      return t('Token has expired.');
+    }
+
+    // Still in time but has it been used before
+    if (db_result(db_query("SELECT count(*) FROM {services_timestamp_nonce}
+        WHERE domain = '%s' AND timestamp = %d AND nonce = '%s'",
+        $domain, $timestamp, $nonce))) {
+      return t('Token has been used previously for a request.');
+    }
+    else{
+      db_query("INSERT INTO {services_timestamp_nonce} (domain, timestamp, nonce)
+        VALUES ('%s', %d, '%s')", $domain, $timestamp, $nonce);
+    }
+
+    $api_key = db_result(db_query("SELECT kid FROM {services_keys} WHERE domain = '%s'", $domain));
+
+    if (!services_keyauth_validate_key($api_key, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash)) {
+      return t('Invalid API key.');
+    }
+  }
+
+  // Add additonal processing for methods requiring session
+  $session_backup = NULL;
+  if ($method['#auth'] && variable_get('services_use_sessid', TRUE)) {
+    $sessid = array_shift($args);
+    if (empty($sessid)) {
+      return t('Invalid sessid.');
+    }
+    $session_backup = services_session_load($sessid);
+  }
+}
\ No newline at end of file
diff --git services_keyauth.info services_keyauth.info
new file mode 100644
index 0000000..b8b4502
--- /dev/null
+++ services_keyauth.info
@@ -0,0 +1,6 @@
+; $Id$
+name = Key Authentication
+description = Provides key authentication for the services module
+package = Services - authentication
+dependencies[] = services
+core = 6.x
\ No newline at end of file
diff --git services_keyauth.install services_keyauth.install
new file mode 100644
index 0000000..96ea628
--- /dev/null
+++ services_keyauth.install
@@ -0,0 +1,86 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_schema().
+ */
+function services_keyauth_schema() {
+  $schema['services_keys'] = array(
+    'description' => 'Stores all Service keys.',
+    'fields' => array(
+      'kid' => array(
+        'description' => 'The service key ID.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => ''
+      ),
+      'title' => array(
+        'description' => 'The title of the service key.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''
+      ),
+      'domain' => array(
+        'description' => 'The domain of the service key.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''
+      ),
+    ),
+    'primary key' => array('kid')
+  );
+
+  $schema['services_timestamp_nonce'] = array(
+    'description' => 'Stores timestamp against nonce for repeat attacks.',
+    'fields' => array(
+      'timestamp' => array(
+        'description' => 'The timestamp used with the Nonce.',
+        'type'        => 'varchar',
+        'length'      => 32,
+        'not null'    => TRUE,
+        'default'     => ''
+      ),
+      'nonce' => array(
+        'description' => 'The random string used on the request.',
+        'type'        => 'varchar',
+        'length'      => 32,
+        'not null'    => TRUE,
+        'default'     => ''
+      ),
+      'domain' => array(
+        'description' => 'The domain that submitted the request.',
+        'type'        => 'varchar',
+        'length'      => 255,
+        'not null'    => TRUE,
+        'default'     => ''
+      ),
+    ),
+    'indexes' => array(
+       'timestamp' => array('timestamp'),
+    ),
+    'primary key' => array('nonce'),
+  );
+  return $schema;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function services_keyauth_install() {
+  if (!db_table_exists('services_keys')) {
+    drupal_install_schema('services_keyauth');
+  }
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function services_keyauth_uninstall() {
+  drupal_uninstall_schema('services_keyauth');
+
+  variable_del('services_use_key');
+  variable_del('services_use_sessid');
+}
\ No newline at end of file
diff --git services_keyauth.module services_keyauth.module
new file mode 100644
index 0000000..ed1d7d3
--- /dev/null
+++ services_keyauth.module
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * Implementation of hook_authentication_info().
+ *
+ * @return array
+ *  The configuration array for the authentication scheme
+ */
+function services_keyauth_authentication_info() {
+  return array(
+    '#file' => 'services_keyauth.inc',
+    '#title' => t('Key authentication'),
+    '#description' => t('The default key-based authentication'),
+    'security_settings' => '_services_keyauth_security_settings',
+    'security_settings_validate' => '_services_keyauth_security_settings_validate',
+    'security_settings_submit' => '_services_keyauth_security_settings_submit',
+    'alter_methods' => '_services_keyauth_alter_methods',
+    'alter_browse_form' => '_services_keyauth_alter_browse_form',
+    'authenticate_call' => '_services_keyauth_authenticate_call',
+  );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function services_keyauth_menu() {
+  $items = array();
+
+  $items['admin/build/services/keys'] = array(
+    'title'             => 'Keys',
+    'description'       => 'Manage application access to site services.',
+    'page callback'     => 'services_keyauth_admin_keys_list',
+    'access callback'   => 'variable_get',
+    'access arguments'  => array('services_use_key', TRUE),
+    'type'              => MENU_LOCAL_TASK,
+    'file'              => 'services_keyauth.admin.inc',
+  );
+  $items['admin/build/services/keys/%'] = array(
+    'title'             => 'Edit key',
+    'access arguments'  => array('administer services'),
+    'page callback'     => 'drupal_get_form',
+    'page arguments'    => array('services_keyauth_admin_keys_form'),
+    'file'              => 'services_keyauth.admin.inc',
+    'type'              => MENU_CALLBACK,
+  );
+  $items['admin/build/services/keys/%/delete'] = array(
+    'access arguments'  => array('administer services'),
+    'page callback'     => 'drupal_get_form',
+    'page arguments'    => array('services_keyauth_admin_keys_delete_confirm', 4),
+    'file'              => 'services_keyauth.admin.inc',
+    'type'              => MENU_CALLBACK,
+  );
+  $items['admin/build/services/keys/list'] = array(
+    'title'             => 'List',
+    'type'              => MENU_DEFAULT_LOCAL_TASK,
+    'access callback'   => 'variable_get',
+    'access arguments'  => array('services_use_key', TRUE),
+    'weight'            => -10,
+  );
+  $items['admin/build/services/keys/add'] = array(
+    'title'             => 'Create key',
+    'page callback'     => 'drupal_get_form',
+    'page arguments'    => array('services_keyauth_admin_keys_form'),
+    'access callback'   => 'variable_get',
+    'access arguments'  => array('services_use_key', TRUE),
+    'type'              => MENU_LOCAL_TASK,
+    'file'              => 'services_keyauth.admin.inc',
+  );
+
+  return $items;
+}
+
+function services_keyauth_validate_key($kid, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash) {
+  $hash_parameters = array_merge(array($timestamp, $domain, $nonce, $method_name), $hash_parameters);
+  $rehash = hash_hmac("sha256", implode(';', $hash_parameters), $kid);
+  return ($rehash == $hash) ? TRUE : FALSE;
+}
+
+function services_keyauth_get_key($kid) {
+  $keys = services_keyauth_get_keys();
+  foreach ($keys as $key) {
+    if ($key->kid == $kid) {
+      return $key;
+    }
+  }
+}
+
+function services_keyauth_get_keys() {
+  static $keys;
+  if (!$keys) {
+    $keys = array();
+    $result = db_query("SELECT * FROM {services_keys}");
+    while ($key = db_fetch_object($result)) {
+      $keys[$key->kid] = $key;
+    }
+  }
+  return $keys;
+}
\ No newline at end of file
