Resolved merge conflicts
diff --git a/binding.gyp b/binding.gyp
index a2431a3..2a5cb1c 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -78,9 +78,10 @@
       "sources": [
         "src/node/ext/byte_buffer.cc",
         "src/node/ext/call.cc",
+        "src/node/ext/call_credentials.cc",
         "src/node/ext/channel.cc",
+        "src/node/ext/channel_credentials.cc",
         "src/node/ext/completion_queue_async_worker.cc",
-        "src/node/ext/credentials.cc",
         "src/node/ext/node_grpc.cc",
         "src/node/ext/server.cc",
         "src/node/ext/server_credentials.cc",
diff --git a/src/node/examples/perf_test.js b/src/node/examples/perf_test.js
index ba8fbf8..fe51e4a 100644
--- a/src/node/examples/perf_test.js
+++ b/src/node/examples/perf_test.js
@@ -42,7 +42,7 @@
   var testServer = interop_server.getServer(0, false);
   testServer.server.start();
   var client = new testProto.TestService('localhost:' + testServer.port,
-                                         grpc.Credentials.createInsecure());
+                                         grpc.credentials.createInsecure());
 
   function runIterations(finish) {
     var start = process.hrtime();
diff --git a/src/node/examples/qps_test.js b/src/node/examples/qps_test.js
index ec968b8..491f473 100644
--- a/src/node/examples/qps_test.js
+++ b/src/node/examples/qps_test.js
@@ -62,7 +62,7 @@
   var testServer = interop_server.getServer(0, false);
   testServer.server.start();
   var client = new testProto.TestService('localhost:' + testServer.port,
-                                         grpc.Credentials.createInsecure());
+                                         grpc.credentials.createInsecure());
 
   var warmup_num = 100;
 
diff --git a/src/node/examples/stock_client.js b/src/node/examples/stock_client.js
index ab9b050..fdd615b 100644
--- a/src/node/examples/stock_client.js
+++ b/src/node/examples/stock_client.js
@@ -39,7 +39,7 @@
  *
  * var StockClient = require('stock_client.js');
  * var stockClient = new StockClient(server_address,
- *                                   grpc.Credentials.createInsecure());
+ *                                   grpc.credentials.createInsecure());
  * stockClient.getLastTradePrice({symbol: 'GOOG'}, function(error, response) {
  *   console.log(error || response);
  * });
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index f98fe24..b63e294 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -39,12 +39,14 @@
 
 #include "grpc/support/log.h"
 #include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
 #include "grpc/support/alloc.h"
 #include "grpc/support/time.h"
 #include "byte_buffer.h"
 #include "call.h"
 #include "channel.h"
 #include "completion_queue_async_worker.h"
+#include "call_credentials.h"
 #include "timeval.h"
 
 using std::unique_ptr;
@@ -502,6 +504,7 @@
   Nan::SetPrototypeMethod(tpl, "cancel", Cancel);
   Nan::SetPrototypeMethod(tpl, "cancelWithStatus", CancelWithStatus);
   Nan::SetPrototypeMethod(tpl, "getPeer", GetPeer);
+  Nan::SetPrototypeMethod(tpl, "setCredentials", SetCredentials);
   fun_tpl.Reset(tpl);
   Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
   Nan::Set(exports, Nan::New("Call").ToLocalChecked(), ctr);
@@ -725,5 +728,26 @@
   info.GetReturnValue().Set(peer_value);
 }
 
+NAN_METHOD(Call::SetCredentials) {
+  Nan::HandleScope scope;
+  if (!HasInstance(info.This())) {
+    return Nan::ThrowTypeError(
+        "setCredentials can only be called on Call objects");
+  }
+  if (!CallCredentials::HasInstance(info[0])) {
+    return Nan::ThrowTypeError(
+        "setCredentials' first argument must be a CallCredentials");
+  }
+  Call *call = ObjectWrap::Unwrap<Call>(info.This());
+  CallCredentials *creds_object = ObjectWrap::Unwrap<CallCredentials>(
+      Nan::To<Object>(info[0]).ToLocalChecked());
+  grpc_credentials *creds = creds_object->GetWrappedCredentials();
+  grpc_call_error error = GRPC_CALL_ERROR;
+  if (creds) {
+    error = grpc_call_set_credentials(call->wrapped_call, creds);
+  }
+  info.GetReturnValue().Set(Nan::New<Uint32>(error));
+}
+
 }  // namespace node
 }  // namespace grpc
diff --git a/src/node/ext/call.h b/src/node/ext/call.h
index 2f8e1f1..dd6c38e 100644
--- a/src/node/ext/call.h
+++ b/src/node/ext/call.h
@@ -73,6 +73,10 @@
   std::vector<unique_ptr<PersistentValue> > handles;
 };
 
+bool CreateMetadataArray(v8::Local<v8::Object> metadata,
+                         grpc_metadata_array *array,
+                         shared_ptr<Resources> resources);
+
 class Op {
  public:
   virtual v8::Local<v8::Value> GetNodeValue() const = 0;
@@ -122,6 +126,7 @@
   static NAN_METHOD(Cancel);
   static NAN_METHOD(CancelWithStatus);
   static NAN_METHOD(GetPeer);
+  static NAN_METHOD(SetCredentials);
   static Nan::Callback *constructor;
   // Used for typechecking instances of this javascript class
   static Nan::Persistent<v8::FunctionTemplate> fun_tpl;
diff --git a/src/node/ext/call_credentials.cc b/src/node/ext/call_credentials.cc
new file mode 100644
index 0000000..839bb56
--- /dev/null
+++ b/src/node/ext/call_credentials.cc
@@ -0,0 +1,259 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <node.h>
+
+#include "grpc/grpc.h"
+#include "grpc/grpc_security.h"
+#include "grpc/support/log.h"
+#include "call_credentials.h"
+#include "call.h"
+
+namespace grpc {
+namespace node {
+
+using Nan::Callback;
+using Nan::EscapableHandleScope;
+using Nan::HandleScope;
+using Nan::Maybe;
+using Nan::MaybeLocal;
+using Nan::ObjectWrap;
+using Nan::Persistent;
+using Nan::Utf8String;
+
+using v8::Exception;
+using v8::External;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::Integer;
+using v8::Local;
+using v8::Object;
+using v8::ObjectTemplate;
+using v8::Value;
+
+Nan::Callback *CallCredentials::constructor;
+Persistent<FunctionTemplate> CallCredentials::fun_tpl;
+
+CallCredentials::CallCredentials(grpc_credentials *credentials)
+    : wrapped_credentials(credentials) {}
+
+CallCredentials::~CallCredentials() {
+  grpc_credentials_release(wrapped_credentials);
+}
+
+void CallCredentials::Init(Local<Object> exports) {
+  HandleScope scope;
+  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
+  tpl->SetClassName(Nan::New("CallCredentials").ToLocalChecked());
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  Nan::SetPrototypeMethod(tpl, "compose", Compose);
+  fun_tpl.Reset(tpl);
+  Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
+  Nan::Set(ctr, Nan::New("createFromPlugin").ToLocalChecked(),
+           Nan::GetFunction(
+               Nan::New<FunctionTemplate>(CreateFromPlugin)).ToLocalChecked());
+  Nan::Set(exports, Nan::New("CallCredentials").ToLocalChecked(), ctr);
+  constructor = new Nan::Callback(ctr);
+}
+
+bool CallCredentials::HasInstance(Local<Value> val) {
+  HandleScope scope;
+  return Nan::New(fun_tpl)->HasInstance(val);
+}
+
+Local<Value> CallCredentials::WrapStruct(grpc_credentials *credentials) {
+  EscapableHandleScope scope;
+  const int argc = 1;
+  if (credentials == NULL) {
+    return scope.Escape(Nan::Null());
+  }
+  Local<Value> argv[argc] = {
+    Nan::New<External>(reinterpret_cast<void *>(credentials))};
+  MaybeLocal<Object> maybe_instance = Nan::NewInstance(
+      constructor->GetFunction(), argc, argv);
+  if (maybe_instance.IsEmpty()) {
+    return scope.Escape(Nan::Null());
+  } else {
+    return scope.Escape(maybe_instance.ToLocalChecked());
+  }
+}
+
+grpc_credentials *CallCredentials::GetWrappedCredentials() {
+  return wrapped_credentials;
+}
+
+NAN_METHOD(CallCredentials::New) {
+  if (info.IsConstructCall()) {
+    if (!info[0]->IsExternal()) {
+      return Nan::ThrowTypeError(
+          "CallCredentials can only be created with the provided functions");
+    }
+    Local<External> ext = info[0].As<External>();
+    grpc_credentials *creds_value =
+        reinterpret_cast<grpc_credentials *>(ext->Value());
+    CallCredentials *credentials = new CallCredentials(creds_value);
+    credentials->Wrap(info.This());
+    info.GetReturnValue().Set(info.This());
+    return;
+  } else {
+    const int argc = 1;
+    Local<Value> argv[argc] = {info[0]};
+    MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
+        argc, argv);
+    if (maybe_instance.IsEmpty()) {
+      // There's probably a pending exception
+      return;
+    } else {
+      info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
+    }
+  }
+}
+
+NAN_METHOD(CallCredentials::Compose) {
+  if (!CallCredentials::HasInstance(info.This())) {
+    return Nan::ThrowTypeError(
+        "compose can only be called on CallCredentials objects");
+  }
+  if (!CallCredentials::HasInstance(info[0])) {
+    return Nan::ThrowTypeError(
+        "compose's first argument must be a CallCredentials object");
+  }
+  CallCredentials *self = ObjectWrap::Unwrap<CallCredentials>(info.This());
+  CallCredentials *other = ObjectWrap::Unwrap<CallCredentials>(
+      Nan::To<Object>(info[0]).ToLocalChecked());
+  grpc_credentials *creds = grpc_composite_credentials_create(
+      self->wrapped_credentials, other->wrapped_credentials, NULL);
+  info.GetReturnValue().Set(WrapStruct(creds));
+}
+
+
+
+NAN_METHOD(CallCredentials::CreateFromPlugin) {
+  if (!info[0]->IsFunction()) {
+    return Nan::ThrowTypeError(
+        "createFromPlugin's argument must be a function");
+  }
+  grpc_metadata_credentials_plugin plugin;
+  plugin_state *state = new plugin_state;
+  state->callback = new Nan::Callback(info[0].As<Function>());
+  plugin.get_metadata = plugin_get_metadata;
+  plugin.destroy = plugin_destroy_state;
+  plugin.state = reinterpret_cast<void*>(state);
+  grpc_credentials *creds = grpc_metadata_credentials_create_from_plugin(plugin,
+                                                                         NULL);
+  info.GetReturnValue().Set(WrapStruct(creds));
+}
+
+NAN_METHOD(PluginCallback) {
+  // Arguments: status code, error details, metadata
+  if (!info[0]->IsUint32()) {
+    return Nan::ThrowTypeError(
+        "The callback's first argument must be a status code");
+  }
+  if (!info[1]->IsString()) {
+    return Nan::ThrowTypeError(
+        "The callback's second argument must be a string");
+  }
+  if (!info[2]->IsObject()) {
+    return Nan::ThrowTypeError(
+        "The callback's third argument must be an object");
+  }
+  shared_ptr<Resources> resources(new Resources);
+  grpc_status_code code = static_cast<grpc_status_code>(
+      Nan::To<uint32_t>(info[0]).FromJust());
+  char *details = *Utf8String(info[1]);
+  grpc_metadata_array array;
+  if (!CreateMetadataArray(Nan::To<Object>(info[2]).ToLocalChecked(),
+                           &array, resources)){
+    return Nan::ThrowError("Failed to parse metadata");
+  }
+  grpc_credentials_plugin_metadata_cb cb =
+      reinterpret_cast<grpc_credentials_plugin_metadata_cb>(
+          Nan::Get(info.Callee(),
+                   Nan::New("cb").ToLocalChecked()
+                   ).ToLocalChecked().As<External>()->Value());
+  void *user_data =
+      Nan::Get(info.Callee(),
+               Nan::New("user_data").ToLocalChecked()
+               ).ToLocalChecked().As<External>()->Value();
+  cb(user_data, array.metadata, array.count, code, details);
+}
+
+NAUV_WORK_CB(SendPluginCallback) {
+  Nan::HandleScope scope;
+  plugin_callback_data *data = reinterpret_cast<plugin_callback_data*>(
+      async->data);
+  // Attach cb and user_data to plugin_callback so that it can access them later
+  v8::Local<v8::Function> plugin_callback = Nan::GetFunction(
+      Nan::New<v8::FunctionTemplate>(PluginCallback)).ToLocalChecked();
+  Nan::Set(plugin_callback, Nan::New("cb").ToLocalChecked(),
+           Nan::New<v8::External>(reinterpret_cast<void*>(data->cb)));
+  Nan::Set(plugin_callback, Nan::New("user_data").ToLocalChecked(),
+           Nan::New<v8::External>(data->user_data));
+  const int argc = 2;
+  v8::Local<v8::Value> argv[argc] = {
+    Nan::New(data->service_url).ToLocalChecked(),
+    plugin_callback
+  };
+  Nan::Callback *callback = data->state->callback;
+  callback->Call(argc, argv);
+  delete data;
+  uv_unref((uv_handle_t *)async);
+  uv_close((uv_handle_t *)async, (uv_close_cb)free);
+}
+
+void plugin_get_metadata(void *state, const char *service_url,
+                         grpc_credentials_plugin_metadata_cb cb,
+                         void *user_data) {
+  uv_async_t *async = static_cast<uv_async_t*>(malloc(sizeof(uv_async_t)));
+  uv_async_init(uv_default_loop(),
+                async,
+                SendPluginCallback);
+  plugin_callback_data *data = new plugin_callback_data;
+  data->state = reinterpret_cast<plugin_state*>(state);
+  data->service_url = service_url;
+  data->cb = cb;
+  data->user_data = user_data;
+  async->data = data;
+  /* libuv says that it will coalesce calls to uv_async_send. If there is ever a
+   * problem with a callback not getting called, that is probably the reason */
+  uv_async_send(async);
+}
+
+void plugin_destroy_state(void *ptr) {
+  plugin_state *state = reinterpret_cast<plugin_state *>(ptr);
+  delete state->callback;
+}
+
+}  // namespace node
+}  // namespace grpc
diff --git a/src/node/ext/credentials.h b/src/node/ext/call_credentials.h
similarity index 70%
copy from src/node/ext/credentials.h
copy to src/node/ext/call_credentials.h
index 1b21117..618292d 100644
--- a/src/node/ext/credentials.h
+++ b/src/node/ext/call_credentials.h
@@ -31,19 +31,17 @@
  *
  */
 
-#ifndef NET_GRPC_NODE_CREDENTIALS_H_
-#define NET_GRPC_NODE_CREDENTIALS_H_
+#ifndef GRPC_NODE_CALL_CREDENTIALS_H_
+#define GRPC_NODE_CALL_CREDENTIALS_H_
 
 #include <node.h>
 #include <nan.h>
-#include "grpc/grpc.h"
 #include "grpc/grpc_security.h"
 
 namespace grpc {
 namespace node {
 
-/* Wrapper class for grpc_credentials structs */
-class Credentials : public Nan::ObjectWrap {
+class CallCredentials : public Nan::ObjectWrap {
  public:
   static void Init(v8::Local<v8::Object> exports);
   static bool HasInstance(v8::Local<v8::Value> val);
@@ -54,21 +52,18 @@
   grpc_credentials *GetWrappedCredentials();
 
  private:
-  explicit Credentials(grpc_credentials *credentials);
-  ~Credentials();
+  explicit CallCredentials(grpc_credentials *credentials);
+  ~CallCredentials();
 
   // Prevent copying
-  Credentials(const Credentials &);
-  Credentials &operator=(const Credentials &);
+  CallCredentials(const CallCredentials &);
+  CallCredentials &operator=(const CallCredentials &);
 
   static NAN_METHOD(New);
-  static NAN_METHOD(CreateDefault);
   static NAN_METHOD(CreateSsl);
-  static NAN_METHOD(CreateComposite);
-  static NAN_METHOD(CreateGce);
-  static NAN_METHOD(CreateFake);
-  static NAN_METHOD(CreateIam);
-  static NAN_METHOD(CreateInsecure);
+  static NAN_METHOD(CreateFromPlugin);
+
+  static NAN_METHOD(Compose);
   static Nan::Callback *constructor;
   // Used for typechecking instances of this javascript class
   static Nan::Persistent<v8::FunctionTemplate> fun_tpl;
@@ -76,7 +71,30 @@
   grpc_credentials *wrapped_credentials;
 };
 
-}  // namespace node
-}  // namespace grpc
+/* Auth metadata plugin functionality */
 
-#endif  // NET_GRPC_NODE_CREDENTIALS_H_
+typedef struct plugin_state {
+  Nan::Callback *callback;
+} plugin_state;
+
+typedef struct plugin_callback_data {
+  plugin_state *state;
+  const char *service_url;
+  grpc_credentials_plugin_metadata_cb cb;
+  void *user_data;
+} plugin_callback_data;
+
+void plugin_get_metadata(void *state, const char *service_url,
+                         grpc_credentials_plugin_metadata_cb cb,
+                         void *user_data);
+
+void plugin_destroy_state(void *state);
+
+NAN_METHOD(PluginCallback);
+
+NAUV_WORK_CB(SendPluginCallback);
+
+}  // namespace node
+}  // namepsace grpc
+
+#endif  // GRPC_NODE_CALL_CREDENTIALS_H_
diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc
index 6eb1e77..a328c01 100644
--- a/src/node/ext/channel.cc
+++ b/src/node/ext/channel.cc
@@ -42,7 +42,7 @@
 #include "call.h"
 #include "channel.h"
 #include "completion_queue_async_worker.h"
-#include "credentials.h"
+#include "channel_credentials.h"
 #include "timeval.h"
 
 namespace grpc {
@@ -112,11 +112,11 @@
     // Owned by the Channel object
     Utf8String host(info[0]);
     grpc_credentials *creds;
-    if (!Credentials::HasInstance(info[1])) {
+    if (!ChannelCredentials::HasInstance(info[1])) {
       return Nan::ThrowTypeError(
-          "Channel's second argument must be a credential");
+          "Channel's second argument must be a ChannelCredentials");
     }
-    Credentials *creds_object = ObjectWrap::Unwrap<Credentials>(
+    ChannelCredentials *creds_object = ObjectWrap::Unwrap<ChannelCredentials>(
         Nan::To<Object>(info[1]).ToLocalChecked());
     creds = creds_object->GetWrappedCredentials();
     grpc_channel_args *channel_args_ptr;
diff --git a/src/node/ext/credentials.cc b/src/node/ext/channel_credentials.cc
similarity index 64%
rename from src/node/ext/credentials.cc
rename to src/node/ext/channel_credentials.cc
index 4f41c92..3d47ff2 100644
--- a/src/node/ext/credentials.cc
+++ b/src/node/ext/channel_credentials.cc
@@ -36,7 +36,9 @@
 #include "grpc/grpc.h"
 #include "grpc/grpc_security.h"
 #include "grpc/support/log.h"
-#include "credentials.h"
+#include "channel_credentials.h"
+#include "call_credentials.h"
+#include "call.h"
 
 namespace grpc {
 namespace node {
@@ -60,51 +62,40 @@
 using v8::ObjectTemplate;
 using v8::Value;
 
-Nan::Callback *Credentials::constructor;
-Persistent<FunctionTemplate> Credentials::fun_tpl;
+Nan::Callback *ChannelCredentials::constructor;
+Persistent<FunctionTemplate> ChannelCredentials::fun_tpl;
 
-Credentials::Credentials(grpc_credentials *credentials)
+ChannelCredentials::ChannelCredentials(grpc_credentials *credentials)
     : wrapped_credentials(credentials) {}
 
-Credentials::~Credentials() {
+ChannelCredentials::~ChannelCredentials() {
   grpc_credentials_release(wrapped_credentials);
 }
 
-void Credentials::Init(Local<Object> exports) {
+void ChannelCredentials::Init(Local<Object> exports) {
   HandleScope scope;
   Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
-  tpl->SetClassName(Nan::New("Credentials").ToLocalChecked());
+  tpl->SetClassName(Nan::New("ChannelCredentials").ToLocalChecked());
   tpl->InstanceTemplate()->SetInternalFieldCount(1);
+  Nan::SetPrototypeMethod(tpl, "compose", Compose);
   fun_tpl.Reset(tpl);
   Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
-  Nan::Set(ctr, Nan::New("createDefault").ToLocalChecked(),
-           Nan::GetFunction(
-               Nan::New<FunctionTemplate>(CreateDefault)).ToLocalChecked());
   Nan::Set(ctr, Nan::New("createSsl").ToLocalChecked(),
            Nan::GetFunction(
                Nan::New<FunctionTemplate>(CreateSsl)).ToLocalChecked());
-  Nan::Set(ctr, Nan::New("createComposite").ToLocalChecked(),
-           Nan::GetFunction(
-               Nan::New<FunctionTemplate>(CreateComposite)).ToLocalChecked());
-  Nan::Set(ctr, Nan::New("createGce").ToLocalChecked(),
-           Nan::GetFunction(
-               Nan::New<FunctionTemplate>(CreateGce)).ToLocalChecked());
-  Nan::Set(ctr, Nan::New("createIam").ToLocalChecked(),
-           Nan::GetFunction(
-               Nan::New<FunctionTemplate>(CreateIam)).ToLocalChecked());
   Nan::Set(ctr, Nan::New("createInsecure").ToLocalChecked(),
            Nan::GetFunction(
                Nan::New<FunctionTemplate>(CreateInsecure)).ToLocalChecked());
-  Nan::Set(exports, Nan::New("Credentials").ToLocalChecked(), ctr);
+  Nan::Set(exports, Nan::New("ChannelCredentials").ToLocalChecked(), ctr);
   constructor = new Nan::Callback(ctr);
 }
 
-bool Credentials::HasInstance(Local<Value> val) {
+bool ChannelCredentials::HasInstance(Local<Value> val) {
   HandleScope scope;
   return Nan::New(fun_tpl)->HasInstance(val);
 }
 
-Local<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
+Local<Value> ChannelCredentials::WrapStruct(grpc_credentials *credentials) {
   EscapableHandleScope scope;
   const int argc = 1;
   Local<Value> argv[argc] = {
@@ -118,20 +109,20 @@
   }
 }
 
-grpc_credentials *Credentials::GetWrappedCredentials() {
+grpc_credentials *ChannelCredentials::GetWrappedCredentials() {
   return wrapped_credentials;
 }
 
-NAN_METHOD(Credentials::New) {
+NAN_METHOD(ChannelCredentials::New) {
   if (info.IsConstructCall()) {
     if (!info[0]->IsExternal()) {
       return Nan::ThrowTypeError(
-          "Credentials can only be created with the provided functions");
+          "ChannelCredentials can only be created with the provided functions");
     }
     Local<External> ext = info[0].As<External>();
     grpc_credentials *creds_value =
         reinterpret_cast<grpc_credentials *>(ext->Value());
-    Credentials *credentials = new Credentials(creds_value);
+    ChannelCredentials *credentials = new ChannelCredentials(creds_value);
     credentials->Wrap(info.This());
     info.GetReturnValue().Set(info.This());
     return;
@@ -149,16 +140,7 @@
   }
 }
 
-NAN_METHOD(Credentials::CreateDefault) {
-  grpc_credentials *creds = grpc_google_default_credentials_create();
-  if (creds == NULL) {
-    info.GetReturnValue().SetNull();
-  } else {
-    info.GetReturnValue().Set(WrapStruct(creds));
-  }
-}
-
-NAN_METHOD(Credentials::CreateSsl) {
+NAN_METHOD(ChannelCredentials::CreateSsl) {
   char *root_certs = NULL;
   grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL};
   if (::node::Buffer::HasInstance(info[0])) {
@@ -188,21 +170,25 @@
   }
 }
 
-NAN_METHOD(Credentials::CreateComposite) {
-  if (!HasInstance(info[0])) {
+NAN_METHOD(ChannelCredentials::Compose) {
+  if (!ChannelCredentials::HasInstance(info.This())) {
     return Nan::ThrowTypeError(
-        "createComposite's first argument must be a Credentials object");
+        "compose can only be called on ChannelCredentials objects");
   }
-  if (!HasInstance(info[1])) {
+  if (!CallCredentials::HasInstance(info[0])) {
     return Nan::ThrowTypeError(
-        "createComposite's second argument must be a Credentials object");
+        "compose's first argument must be a CallCredentials object");
   }
-  Credentials *creds1 = ObjectWrap::Unwrap<Credentials>(
+  ChannelCredentials *self = ObjectWrap::Unwrap<ChannelCredentials>(
+      info.This());
+  if (self->wrapped_credentials == NULL) {
+    return Nan::ThrowTypeError(
+        "Cannot compose insecure credential");
+  }
+  CallCredentials *other = ObjectWrap::Unwrap<CallCredentials>(
       Nan::To<Object>(info[0]).ToLocalChecked());
-  Credentials *creds2 = ObjectWrap::Unwrap<Credentials>(
-      Nan::To<Object>(info[1]).ToLocalChecked());
   grpc_credentials *creds = grpc_composite_credentials_create(
-      creds1->wrapped_credentials, creds2->wrapped_credentials, NULL);
+      self->wrapped_credentials, other->GetWrappedCredentials(), NULL);
   if (creds == NULL) {
     info.GetReturnValue().SetNull();
   } else {
@@ -210,35 +196,7 @@
   }
 }
 
-NAN_METHOD(Credentials::CreateGce) {
-  Nan::HandleScope scope;
-  grpc_credentials *creds = grpc_google_compute_engine_credentials_create(NULL);
-  if (creds == NULL) {
-    info.GetReturnValue().SetNull();
-  } else {
-    info.GetReturnValue().Set(WrapStruct(creds));
-  }
-}
-
-NAN_METHOD(Credentials::CreateIam) {
-  if (!info[0]->IsString()) {
-    return Nan::ThrowTypeError("createIam's first argument must be a string");
-  }
-  if (!info[1]->IsString()) {
-    return Nan::ThrowTypeError("createIam's second argument must be a string");
-  }
-  Utf8String auth_token(info[0]);
-  Utf8String auth_selector(info[1]);
-  grpc_credentials *creds =
-      grpc_google_iam_credentials_create(*auth_token, *auth_selector, NULL);
-  if (creds == NULL) {
-    info.GetReturnValue().SetNull();
-  } else {
-    info.GetReturnValue().Set(WrapStruct(creds));
-  }
-}
-
-NAN_METHOD(Credentials::CreateInsecure) {
+NAN_METHOD(ChannelCredentials::CreateInsecure) {
   info.GetReturnValue().Set(WrapStruct(NULL));
 }
 
diff --git a/src/node/ext/credentials.h b/src/node/ext/channel_credentials.h
similarity index 83%
rename from src/node/ext/credentials.h
rename to src/node/ext/channel_credentials.h
index 1b21117..31ea098 100644
--- a/src/node/ext/credentials.h
+++ b/src/node/ext/channel_credentials.h
@@ -31,8 +31,8 @@
  *
  */
 
-#ifndef NET_GRPC_NODE_CREDENTIALS_H_
-#define NET_GRPC_NODE_CREDENTIALS_H_
+#ifndef NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_
+#define NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_
 
 #include <node.h>
 #include <nan.h>
@@ -43,7 +43,7 @@
 namespace node {
 
 /* Wrapper class for grpc_credentials structs */
-class Credentials : public Nan::ObjectWrap {
+class ChannelCredentials : public Nan::ObjectWrap {
  public:
   static void Init(v8::Local<v8::Object> exports);
   static bool HasInstance(v8::Local<v8::Value> val);
@@ -54,21 +54,18 @@
   grpc_credentials *GetWrappedCredentials();
 
  private:
-  explicit Credentials(grpc_credentials *credentials);
-  ~Credentials();
+  explicit ChannelCredentials(grpc_credentials *credentials);
+  ~ChannelCredentials();
 
   // Prevent copying
-  Credentials(const Credentials &);
-  Credentials &operator=(const Credentials &);
+  ChannelCredentials(const ChannelCredentials &);
+  ChannelCredentials &operator=(const ChannelCredentials &);
 
   static NAN_METHOD(New);
-  static NAN_METHOD(CreateDefault);
   static NAN_METHOD(CreateSsl);
-  static NAN_METHOD(CreateComposite);
-  static NAN_METHOD(CreateGce);
-  static NAN_METHOD(CreateFake);
-  static NAN_METHOD(CreateIam);
   static NAN_METHOD(CreateInsecure);
+
+  static NAN_METHOD(Compose);
   static Nan::Callback *constructor;
   // Used for typechecking instances of this javascript class
   static Nan::Persistent<v8::FunctionTemplate> fun_tpl;
@@ -79,4 +76,4 @@
 }  // namespace node
 }  // namespace grpc
 
-#endif  // NET_GRPC_NODE_CREDENTIALS_H_
+#endif  // NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_
diff --git a/src/node/ext/node_grpc.cc b/src/node/ext/node_grpc.cc
index 8ea0c64..5b5f3c1 100644
--- a/src/node/ext/node_grpc.cc
+++ b/src/node/ext/node_grpc.cc
@@ -37,10 +37,11 @@
 #include "grpc/grpc.h"
 
 #include "call.h"
+#include "call_credentials.h"
 #include "channel.h"
+#include "channel_credentials.h"
 #include "server.h"
 #include "completion_queue_async_worker.h"
-#include "credentials.h"
 #include "server_credentials.h"
 
 using v8::Local;
@@ -240,10 +241,11 @@
   InitWriteFlags(exports);
 
   grpc::node::Call::Init(exports);
+  grpc::node::CallCredentials::Init(exports);
   grpc::node::Channel::Init(exports);
+  grpc::node::ChannelCredentials::Init(exports);
   grpc::node::Server::Init(exports);
   grpc::node::CompletionQueueAsyncWorker::Init(exports);
-  grpc::node::Credentials::Init(exports);
   grpc::node::ServerCredentials::Init(exports);
 }
 
diff --git a/src/node/index.js b/src/node/index.js
index c49fdc8..591d9dd 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -94,32 +94,6 @@
 };
 
 /**
- * Get a function that a client can use to update metadata with authentication
- * information from a Google Auth credential object, which comes from the
- * google-auth-library.
- * @param {Object} credential The credential object to use
- * @return {function(Object, callback)} Metadata updater function
- */
-exports.getGoogleAuthDelegate = function getGoogleAuthDelegate(credential) {
-  /**
-   * Update a metadata object with authentication information.
-   * @param {string} authURI The uri to authenticate to
-   * @param {Object} metadata Metadata object
-   * @param {function(Error, Object)} callback
-   */
-  return function updateMetadata(authURI, metadata, callback) {
-    credential.getRequestMetadata(authURI, function(err, header) {
-      if (err) {
-        callback(err);
-        return;
-      }
-      metadata.add('authorization', header.Authorization);
-      callback(null, metadata);
-    });
-  };
-};
-
-/**
  * @see module:src/server.Server
  */
 exports.Server = server.Server;
@@ -152,7 +126,7 @@
 /**
  * Credentials factories
  */
-exports.Credentials = grpc.Credentials;
+exports.credentials = require('./src/credentials.js');
 
 /**
  * ServerCredentials factories
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index da614ad..14cc6c0 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -354,33 +354,26 @@
  *     primarily for use with mocha
  */
 function authTest(expected_user, scope, client, done) {
-  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
+  var arg = {
+    response_type: 'COMPRESSABLE',
+    response_size: 314159,
+    payload: {
+      body: zeroBuffer(271828)
+    },
+    fill_username: true,
+    fill_oauth_scope: true
+  };
+  client.unaryCall(arg, function(err, resp) {
     assert.ifError(err);
-    if (credential.createScopedRequired() && scope) {
-      credential = credential.createScoped(scope);
+    assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
+    assert.strictEqual(resp.payload.body.length, 314159);
+    assert.strictEqual(resp.username, expected_user);
+    if (scope) {
+      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
     }
-    client.$updateMetadata = grpc.getGoogleAuthDelegate(credential);
-    var arg = {
-      response_type: 'COMPRESSABLE',
-      response_size: 314159,
-      payload: {
-        body: zeroBuffer(271828)
-      },
-      fill_username: true,
-      fill_oauth_scope: true
-    };
-    client.unaryCall(arg, function(err, resp) {
-      assert.ifError(err);
-      assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
-      assert.strictEqual(resp.payload.body.length, 314159);
-      assert.strictEqual(resp.username, expected_user);
-      if (scope) {
-        assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
-      }
-      if (done) {
-        done();
-      }
-    });
+    if (done) {
+      done();
+    }
   });
 }
 
@@ -419,25 +412,86 @@
   });
 }
 
+function perRpcAuthTest(expected_user, scope, per_rpc, client, done) {
+  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
+    assert.ifError(err);
+    var arg = {
+      fill_username: true,
+      fill_oauth_scope: true
+    };
+    if (credential.createScopedRequired() && scope) {
+      credential = credential.createScoped(scope);
+    }
+    var creds = grpc.credentials.createFromGoogleCredential(credential);
+    client.unaryCall(arg, function(err, resp) {
+      assert.ifError(err);
+      assert.strictEqual(resp.username, expected_user);
+      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
+      if (done) {
+        done();
+      }
+    }, null, {credentials: creds});
+  });
+}
+
+function getApplicationCreds(scope, callback) {
+  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
+    if (err) {
+      callback(err);
+      return;
+    }
+    if (credential.createScopedRequired() && scope) {
+      credential = credential.createScoped(scope);
+    }
+    callback(null, grpc.credentials.createFromGoogleCredential(credential));
+  });
+}
+
+function getOauth2Creds(scope, callback) {
+  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
+    if (err) {
+      callback(err);
+      return;
+    }
+    credential = credential.createScoped(scope);
+    credential.getAccessToken(function(err, token) {
+      if (err) {
+        callback(err);
+        return;
+      }
+      var updateMd = function(service_url, callback) {
+        var metadata = new grpc.Metadata();
+        metadata.add('authorization', 'Bearer ' + token);
+        callback(null, metadata);
+      };
+      callback(null, grpc.credentials.createFromMetadataGenerator(updateMd));
+    });
+  });
+}
+
 /**
  * Map from test case names to test functions
  */
 var test_cases = {
-  empty_unary: emptyUnary,
-  large_unary: largeUnary,
-  client_streaming: clientStreaming,
-  server_streaming: serverStreaming,
-  ping_pong: pingPong,
-  empty_stream: emptyStream,
-  cancel_after_begin: cancelAfterBegin,
-  cancel_after_first_response: cancelAfterFirstResponse,
-  timeout_on_sleeping_server: timeoutOnSleepingServer,
-  custom_metadata: customMetadata,
-  compute_engine_creds: _.partial(authTest, COMPUTE_ENGINE_USER, null),
-  service_account_creds: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
-  jwt_token_creds: _.partial(authTest, AUTH_USER, null),
-  oauth2_auth_token: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
-  per_rpc_creds: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, true)
+  empty_unary: {run: emptyUnary},
+  large_unary: {run: largeUnary},
+  client_streaming: {run: clientStreaming},
+  server_streaming: {run: serverStreaming},
+  ping_pong: {run: pingPong},
+  empty_stream: {run: emptyStream},
+  cancel_after_begin: {run: cancelAfterBegin},
+  cancel_after_first_response: {run: cancelAfterFirstResponse},
+  timeout_on_sleeping_server: {run: timeoutOnSleepingServer},
+  custom_metadata: {run: customMetadata},
+  compute_engine_creds: {run: _.partial(authTest, COMPUTE_ENGINE_USER, null),
+                         getCreds: _.partial(getApplicationCreds, null)},
+  service_account_creds: {run: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
+                          getCreds: _.partial(getApplicationCreds, AUTH_SCOPE)},
+  jwt_token_creds: {run: _.partial(authTest, AUTH_USER, null),
+                    getCreds: _.partial(getApplicationCreds, null)},
+  oauth2_auth_token: {run: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
+                      getCreds: _.partial(getOauth2Creds, AUTH_SCOPE)},
+  per_rpc_creds: {run: _.partial(perRpcAuthTest, AUTH_USER, AUTH_SCOPE, true)}
 };
 
 /**
@@ -463,17 +517,30 @@
       ca_path = process.env.SSL_CERT_FILE;
     }
     var ca_data = fs.readFileSync(ca_path);
-    creds = grpc.Credentials.createSsl(ca_data);
+    creds = grpc.credentials.createSsl(ca_data);
     if (host_override) {
       options['grpc.ssl_target_name_override'] = host_override;
       options['grpc.default_authority'] = host_override;
     }
   } else {
-    creds = grpc.Credentials.createInsecure();
+    creds = grpc.credentials.createInsecure();
   }
-  var client = new testProto.TestService(address, creds, options);
+  var test = test_cases[test_case];
 
-  test_cases[test_case](client, done);
+  var execute = function(err, creds) {
+    assert.ifError(err);
+    var client = new testProto.TestService(address, creds, options);
+    test.run(client, done);
+  };
+
+  if (test.getCreds) {
+    test.getCreds(function(err, new_creds) {
+      execute(err, grpc.credentials.combineChannelCredentials(
+          creds, new_creds));
+    });
+  } else {
+    execute(null, creds);
+  }
 }
 
 if (require.main === module) {
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 33ddb3c..909376e 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -233,17 +233,23 @@
   var host;
   var parent;
   var propagate_flags;
+  var credentials;
   if (options) {
     deadline = options.deadline;
     host = options.host;
     parent = _.get(options, 'parent.call');
     propagate_flags = options.propagate_flags;
+    credentials = options.credentials;
   }
   if (deadline === undefined) {
     deadline = Infinity;
   }
-  return new grpc.Call(channel, method, deadline, host,
-                       parent, propagate_flags);
+  var call = new grpc.Call(channel, method, deadline, host,
+                           parent, propagate_flags);
+  if (credentials) {
+    call.setCredentials(credentials);
+  }
+  return call;
 }
 
 /**
@@ -282,60 +288,53 @@
     emitter.getPeer = function getPeer() {
       return call.getPeer();
     };
-    this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
-      if (error) {
-        call.cancel();
-        callback(error);
-        return;
-      }
-      var client_batch = {};
-      var message = serialize(argument);
-      if (options) {
-        message.grpcWriteFlags = options.flags;
-      }
-      client_batch[grpc.opType.SEND_INITIAL_METADATA] =
-          metadata._getCoreRepresentation();
-      client_batch[grpc.opType.SEND_MESSAGE] = message;
-      client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
-      client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-      client_batch[grpc.opType.RECV_MESSAGE] = true;
-      client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-      call.startBatch(client_batch, function(err, response) {
-        response.status.metadata = Metadata._fromCoreRepresentation(
-              response.status.metadata);
-        var status = response.status;
-        var error;
-        var deserialized;
-        if (status.code === grpc.status.OK) {
-          if (err) {
-            // Got a batch error, but OK status. Something went wrong
-            callback(err);
-            return;
-          } else {
-            try {
-              deserialized = deserialize(response.read);
-            } catch (e) {
-              /* Change status to indicate bad server response. This will result
-               * in passing an error to the callback */
-              status = {
-                code: grpc.status.INTERNAL,
-                details: 'Failed to parse server response'
-              };
-            }
+    var client_batch = {};
+    var message = serialize(argument);
+    if (options) {
+      message.grpcWriteFlags = options.flags;
+    }
+    client_batch[grpc.opType.SEND_INITIAL_METADATA] =
+        metadata._getCoreRepresentation();
+    client_batch[grpc.opType.SEND_MESSAGE] = message;
+    client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
+    client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+    client_batch[grpc.opType.RECV_MESSAGE] = true;
+    client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+    call.startBatch(client_batch, function(err, response) {
+      response.status.metadata = Metadata._fromCoreRepresentation(
+          response.status.metadata);
+      var status = response.status;
+      var error;
+      var deserialized;
+      if (status.code === grpc.status.OK) {
+        if (err) {
+          // Got a batch error, but OK status. Something went wrong
+          callback(err);
+          return;
+        } else {
+          try {
+            deserialized = deserialize(response.read);
+          } catch (e) {
+            /* Change status to indicate bad server response. This will result
+             * in passing an error to the callback */
+            status = {
+              code: grpc.status.INTERNAL,
+              details: 'Failed to parse server response'
+            };
           }
         }
-        if (status.code !== grpc.status.OK) {
-          error = new Error(response.status.details);
-          error.code = status.code;
-          error.metadata = status.metadata;
-          callback(error);
-        } else {
-          callback(null, deserialized);
-        }
-        emitter.emit('status', status);
-        emitter.emit('metadata', Metadata._fromCoreRepresentation(
-            response.metadata));
-      });
+      }
+      if (status.code !== grpc.status.OK) {
+        error = new Error(response.status.details);
+        error.code = status.code;
+        error.metadata = status.metadata;
+        callback(error);
+      } else {
+        callback(null, deserialized);
+      }
+      emitter.emit('status', status);
+      emitter.emit('metadata', Metadata._fromCoreRepresentation(
+          response.metadata));
     });
     return emitter;
   }
@@ -371,62 +370,55 @@
       metadata = metadata.clone();
     }
     var stream = new ClientWritableStream(call, serialize);
-    this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
-      if (error) {
-        call.cancel();
-        callback(error);
+    var metadata_batch = {};
+    metadata_batch[grpc.opType.SEND_INITIAL_METADATA] =
+        metadata._getCoreRepresentation();
+    metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+    call.startBatch(metadata_batch, function(err, response) {
+      if (err) {
+        // The call has stopped for some reason. A non-OK status will arrive
+        // in the other batch.
         return;
       }
-      var metadata_batch = {};
-      metadata_batch[grpc.opType.SEND_INITIAL_METADATA] =
-          metadata._getCoreRepresentation();
-      metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-      call.startBatch(metadata_batch, function(err, response) {
+      stream.emit('metadata', Metadata._fromCoreRepresentation(
+          response.metadata));
+    });
+    var client_batch = {};
+    client_batch[grpc.opType.RECV_MESSAGE] = true;
+    client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+    call.startBatch(client_batch, function(err, response) {
+      response.status.metadata = Metadata._fromCoreRepresentation(
+          response.status.metadata);
+      var status = response.status;
+      var error;
+      var deserialized;
+      if (status.code === grpc.status.OK) {
         if (err) {
-          // The call has stopped for some reason. A non-OK status will arrive
-          // in the other batch.
+          // Got a batch error, but OK status. Something went wrong
+          callback(err);
           return;
-        }
-        stream.emit('metadata', Metadata._fromCoreRepresentation(
-            response.metadata));
-      });
-      var client_batch = {};
-      client_batch[grpc.opType.RECV_MESSAGE] = true;
-      client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-      call.startBatch(client_batch, function(err, response) {
-        response.status.metadata = Metadata._fromCoreRepresentation(
-              response.status.metadata);
-        var status = response.status;
-        var error;
-        var deserialized;
-        if (status.code === grpc.status.OK) {
-          if (err) {
-            // Got a batch error, but OK status. Something went wrong
-            callback(err);
-            return;
-          } else {
-            try {
-              deserialized = deserialize(response.read);
-            } catch (e) {
-              /* Change status to indicate bad server response. This will result
-               * in passing an error to the callback */
-              status = {
-                code: grpc.status.INTERNAL,
-                details: 'Failed to parse server response'
-              };
-            }
+        } else {
+          try {
+            deserialized = deserialize(response.read);
+          } catch (e) {
+            /* Change status to indicate bad server response. This will result
+             * in passing an error to the callback */
+            status = {
+              code: grpc.status.INTERNAL,
+              details: 'Failed to parse server response'
+            };
           }
         }
-        if (status.code !== grpc.status.OK) {
-          error = new Error(response.status.details);
-          error.code = status.code;
-          error.metadata = status.metadata;
-          callback(error);
-        } else {
-          callback(null, deserialized);
-        }
-        stream.emit('status', status);
-      });
+      }
+      if (status.code !== grpc.status.OK) {
+        error = new Error(response.status.details);
+        error.code = status.code;
+        error.metadata = status.metadata;
+        callback(error);
+      } else {
+        callback(null, deserialized);
+      }
+      stream.emit('status', status);
     });
     return stream;
   }
@@ -462,51 +454,44 @@
       metadata = metadata.clone();
     }
     var stream = new ClientReadableStream(call, deserialize);
-    this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
-      if (error) {
-        call.cancel();
-        stream.emit('error', error);
+    var start_batch = {};
+    var message = serialize(argument);
+    if (options) {
+      message.grpcWriteFlags = options.flags;
+    }
+    start_batch[grpc.opType.SEND_INITIAL_METADATA] =
+        metadata._getCoreRepresentation();
+    start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+    start_batch[grpc.opType.SEND_MESSAGE] = message;
+    start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
+    call.startBatch(start_batch, function(err, response) {
+      if (err) {
+        // The call has stopped for some reason. A non-OK status will arrive
+        // in the other batch.
         return;
       }
-      var start_batch = {};
-      var message = serialize(argument);
-      if (options) {
-        message.grpcWriteFlags = options.flags;
-      }
-      start_batch[grpc.opType.SEND_INITIAL_METADATA] =
-          metadata._getCoreRepresentation();
-      start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-      start_batch[grpc.opType.SEND_MESSAGE] = message;
-      start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
-      call.startBatch(start_batch, function(err, response) {
+      stream.emit('metadata', Metadata._fromCoreRepresentation(
+          response.metadata));
+    });
+    var status_batch = {};
+    status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+    call.startBatch(status_batch, function(err, response) {
+      response.status.metadata = Metadata._fromCoreRepresentation(
+          response.status.metadata);
+      stream.emit('status', response.status);
+      if (response.status.code !== grpc.status.OK) {
+        var error = new Error(response.status.details);
+        error.code = response.status.code;
+        error.metadata = response.status.metadata;
+        stream.emit('error', error);
+        return;
+      } else {
         if (err) {
-          // The call has stopped for some reason. A non-OK status will arrive
-          // in the other batch.
+          // Got a batch error, but OK status. Something went wrong
+          stream.emit('error', err);
           return;
         }
-        stream.emit('metadata', Metadata._fromCoreRepresentation(
-            response.metadata));
-      });
-      var status_batch = {};
-      status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-      call.startBatch(status_batch, function(err, response) {
-        response.status.metadata = Metadata._fromCoreRepresentation(
-              response.status.metadata);
-        stream.emit('status', response.status);
-        if (response.status.code !== grpc.status.OK) {
-          var error = new Error(response.status.details);
-          error.code = response.status.code;
-          error.metadata = response.status.metadata;
-          stream.emit('error', error);
-          return;
-        } else {
-          if (err) {
-            // Got a batch error, but OK status. Something went wrong
-            stream.emit('error', err);
-            return;
-          }
-        }
-      });
+      }
     });
     return stream;
   }
@@ -540,45 +525,38 @@
       metadata = metadata.clone();
     }
     var stream = new ClientDuplexStream(call, serialize, deserialize);
-    this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) {
-      if (error) {
-        call.cancel();
-        stream.emit('error', error);
+    var start_batch = {};
+    start_batch[grpc.opType.SEND_INITIAL_METADATA] =
+        metadata._getCoreRepresentation();
+    start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+    call.startBatch(start_batch, function(err, response) {
+      if (err) {
+        // The call has stopped for some reason. A non-OK status will arrive
+        // in the other batch.
         return;
       }
-      var start_batch = {};
-      start_batch[grpc.opType.SEND_INITIAL_METADATA] =
-          metadata._getCoreRepresentation();
-      start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
-      call.startBatch(start_batch, function(err, response) {
+      stream.emit('metadata', Metadata._fromCoreRepresentation(
+          response.metadata));
+    });
+    var status_batch = {};
+    status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+    call.startBatch(status_batch, function(err, response) {
+      response.status.metadata = Metadata._fromCoreRepresentation(
+          response.status.metadata);
+      stream.emit('status', response.status);
+      if (response.status.code !== grpc.status.OK) {
+        var error = new Error(response.status.details);
+        error.code = response.status.code;
+        error.metadata = response.status.metadata;
+        stream.emit('error', error);
+        return;
+      } else {
         if (err) {
-          // The call has stopped for some reason. A non-OK status will arrive
-          // in the other batch.
+          // Got a batch error, but OK status. Something went wrong
+          stream.emit('error', err);
           return;
         }
-        stream.emit('metadata', Metadata._fromCoreRepresentation(
-            response.metadata));
-      });
-      var status_batch = {};
-      status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
-      call.startBatch(status_batch, function(err, response) {
-        response.status.metadata = Metadata._fromCoreRepresentation(
-              response.status.metadata);
-        stream.emit('status', response.status);
-        if (response.status.code !== grpc.status.OK) {
-          var error = new Error(response.status.details);
-          error.code = response.status.code;
-          error.metadata = response.status.metadata;
-          stream.emit('error', error);
-          return;
-        } else {
-          if (err) {
-            // Got a batch error, but OK status. Something went wrong
-            stream.emit('error', err);
-            return;
-          }
-        }
-      });
+      }
     });
     return stream;
   }
@@ -618,15 +596,8 @@
    * @param {grpc.Credentials} credentials Credentials to use to connect
    *     to the server
    * @param {Object} options Options to pass to the underlying channel
-   * @param {function(string, Object, function)=} updateMetadata function to
-   *     update the metadata for each request
    */
-  function Client(address, credentials, options, updateMetadata) {
-    if (!updateMetadata) {
-      updateMetadata = function(uri, metadata, callback) {
-        callback(null, metadata);
-      };
-    }
+  function Client(address, credentials, options) {
     if (!options) {
       options = {};
     }
@@ -634,11 +605,6 @@
     /* Private fields use $ as a prefix instead of _ because it is an invalid
      * prefix of a method name */
     this.$channel = new grpc.Channel(address, credentials, options);
-    // Remove the optional DNS scheme, trailing port, and trailing backslash
-    address = address.replace(/^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$/, '$2');
-    this.$server_address = address;
-    this.$auth_uri = 'https://' + this.$server_address + '/' + serviceName;
-    this.$updateMetadata = updateMetadata;
   }
 
   _.each(methods, function(attrs, name) {
diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js
new file mode 100644
index 0000000..97dd973
--- /dev/null
+++ b/src/node/src/credentials.js
@@ -0,0 +1,140 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/**
+ * Credentials module
+ * @module
+ */
+
+'use strict';
+
+var grpc = require('bindings')('grpc_node.node');
+
+var CallCredentials = grpc.CallCredentials;
+
+var ChannelCredentials = grpc.ChannelCredentials;
+
+var Metadata = require('./metadata.js');
+
+/**
+ * Create an SSL Credentials object. If using a client-side certificate, both
+ * the second and third arguments must be passed.
+ * @param {Buffer} root_certs The root certificate data
+ * @param {Buffer=} private_key The client certificate private key, if
+ *     applicable
+ * @param {Buffer=} cert_chain The client certificate cert chain, if applicable
+ * @return {ChannelCredentials} The SSL Credentials object
+ */
+exports.createSsl = ChannelCredentials.createSsl;
+
+/**
+ * Create a gRPC credentials object from a metadata generation function. This
+ * function gets the service URL and a callback as parameters. The error
+ * passed to the callback can optionally have a 'code' value attached to it,
+ * which corresponds to a status code that this library uses.
+ * @param {function(String, function(Error, Metadata))} metadata_generator The
+ *     function that generates metadata
+ * @return {CallCredentials} The credentials object
+ */
+exports.createFromMetadataGenerator = function(metadata_generator) {
+  return CallCredentials.createFromPlugin(function(service_url, callback) {
+    metadata_generator(service_url, function(error, metadata) {
+      var code = grpc.status.OK;
+      var message = '';
+      if (error) {
+        message = error.message;
+        if (error.hasOwnProperty('code')) {
+          code = error.code;
+        }
+      }
+      callback(code, message, metadata._getCoreRepresentation());
+    });
+  });
+};
+
+/**
+ * Create a gRPC credential from a Google credential object.
+ * @param {Object} google_credential The Google credential object to use
+ * @return {CallCredentials} The resulting credentials object
+ */
+exports.createFromGoogleCredential = function(google_credential) {
+  return exports.createFromMetadataGenerator(function(service_url, callback) {
+    google_credential.getRequestMetadata(service_url, function(err, header) {
+      if (err) {
+        callback(err);
+        return;
+      }
+      var metadata = new Metadata();
+      metadata.add('authorization', header.Authorization);
+      callback(null, metadata);
+    });
+  });
+};
+
+/**
+ * Combine a ChannelCredentials with any number of CallCredentials into a single
+ * ChannelCredentials object.
+ * @param {ChannelCredentials} channel_credential The ChannelCredentials to
+ *     start with
+ * @param {...CallCredentials} credentials The CallCredentials to compose
+ * @return ChannelCredentials A credentials object that combines all of the
+ *     input credentials
+ */
+exports.combineChannelCredentials = function(channel_credential) {
+  var current = channel_credential;
+  for (var i = 1; i < arguments.length; i++) {
+    current = current.compose(arguments[i]);
+  }
+  return current;
+};
+
+/**
+ * Combine any number of CallCredentials into a single CallCredentials object
+ * @param {...CallCredentials} credentials the CallCredentials to compose
+ * @return CallCredentials A credentials object that combines all of the input
+ *     credentials
+ */
+exports.combineCallCredentials = function() {
+  var current = arguments[0];
+  for (var i = 1; i < arguments.length; i++) {
+    current = current.compose(arguments[i]);
+  }
+  return current;
+}
+
+/**
+ * Create an insecure credentials object. This is used to create a channel that
+ * does not use SSL.
+ * @return {ChannelCredentials} The insecure credentials object
+ */
+exports.createInsecure = ChannelCredentials.createInsecure;
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
index ec50218..c316fe7 100644
--- a/src/node/test/call_test.js
+++ b/src/node/test/call_test.js
@@ -48,7 +48,7 @@
   return deadline;
 }
 
-var insecureCreds = grpc.Credentials.createInsecure();
+var insecureCreds = grpc.ChannelCredentials.createInsecure();
 
 describe('call', function() {
   var channel;
diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js
index 4418dff..b86f89b 100644
--- a/src/node/test/channel_test.js
+++ b/src/node/test/channel_test.js
@@ -56,7 +56,7 @@
     }
   };
 }
-var insecureCreds = grpc.Credentials.createInsecure();
+var insecureCreds = grpc.ChannelCredentials.createInsecure();
 
 describe('channel', function() {
   describe('constructor', function() {
@@ -149,12 +149,13 @@
     afterEach(function() {
       channel.close();
     });
-    it('should time out if called alone', function(done) {
+    it.only('should time out if called alone', function(done) {
       var old_state = channel.getConnectivityState();
       var deadline = new Date();
       deadline.setSeconds(deadline.getSeconds() + 1);
       channel.watchConnectivityState(old_state, deadline, function(err, value) {
         assert(err);
+        console.log('Callback from watchConnectivityState');
         done();
       });
     });
diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js
new file mode 100644
index 0000000..8eb91ee
--- /dev/null
+++ b/src/node/test/credentials_test.js
@@ -0,0 +1,243 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+'use strict';
+
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+
+var grpc = require('..');
+
+/**
+ * This is used for testing functions with multiple asynchronous calls that
+ * can happen in different orders. This should be passed the number of async
+ * function invocations that can occur last, and each of those should call this
+ * function's return value
+ * @param {function()} done The function that should be called when a test is
+ *     complete.
+ * @param {number} count The number of calls to the resulting function if the
+ *     test passes.
+ * @return {function()} The function that should be called at the end of each
+ *     sequence of asynchronous functions.
+ */
+function multiDone(done, count) {
+  return function() {
+    count -= 1;
+    if (count <= 0) {
+      done();
+    }
+  };
+}
+
+describe('client credentials', function() {
+  var Client;
+  var server;
+  var port;
+  var client_ssl_creds;
+  var client_options = {};
+  before(function() {
+    var proto = grpc.load(__dirname + '/test_service.proto');
+    server = new grpc.Server();
+    server.addProtoService(proto.TestService.service, {
+      unary: function(call, cb) {
+        call.sendMetadata(call.metadata);
+        cb(null, {});
+      },
+      clientStream: function(stream, cb){
+        stream.on('data', function(data) {});
+        stream.on('end', function() {
+          stream.sendMetadata(stream.metadata);
+          cb(null, {});
+        });
+      },
+      serverStream: function(stream) {
+        stream.sendMetadata(stream.metadata);
+        stream.end();
+      },
+      bidiStream: function(stream) {
+        stream.on('data', function(data) {});
+        stream.on('end', function() {
+          stream.sendMetadata(stream.metadata);
+          stream.end();
+        });
+      }
+    });
+    var key_path = path.join(__dirname, './data/server1.key');
+    var pem_path = path.join(__dirname, './data/server1.pem');
+    var key_data = fs.readFileSync(key_path);
+    var pem_data = fs.readFileSync(pem_path);
+    var creds = grpc.ServerCredentials.createSsl(null,
+                                                 [{private_key: key_data,
+                                                   cert_chain: pem_data}]);
+    //creds = grpc.ServerCredentials.createInsecure();
+    port = server.bind('localhost:0', creds);
+    server.start();
+
+    Client = proto.TestService;
+    var ca_path = path.join(__dirname, '../test/data/ca.pem');
+    var ca_data = fs.readFileSync(ca_path);
+    client_ssl_creds = grpc.credentials.createSsl(ca_data);
+    var host_override = 'foo.test.google.fr';
+    client_options['grpc.ssl_target_name_override'] = host_override;
+    client_options['grpc.default_authority'] = host_override;
+  });
+  after(function() {
+    server.forceShutdown();
+  });
+  it('Should accept SSL creds for a client', function(done) {
+    var client = new Client('localhost:' + port, client_ssl_creds,
+                            client_options);
+    client.unary({}, function(err, data) {
+      assert.ifError(err);
+      done();
+    });
+  });
+  it('Should update metadata with SSL creds', function(done) {
+    var metadataUpdater = function(service_url, callback) {
+      var metadata = new grpc.Metadata();
+      metadata.set('plugin_key', 'plugin_value');
+      callback(null, metadata);
+    };
+    var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
+    var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
+                                                             creds);
+    var client = new Client('localhost:' + port, combined_creds,
+                            client_options);
+    var call = client.unary({}, function(err, data) {
+      assert.ifError(err);
+    });
+    call.on('metadata', function(metadata) {
+      assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+      done();
+    });
+  });
+  it('Should update metadata for two simultaneous calls', function(done) {
+    done = multiDone(done, 2);
+    var metadataUpdater = function(service_url, callback) {
+      var metadata = new grpc.Metadata();
+      metadata.set('plugin_key', 'plugin_value');
+      callback(null, metadata);
+    };
+    var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
+    var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
+                                                             creds);
+    var client = new Client('localhost:' + port, combined_creds,
+                            client_options);
+    var call = client.unary({}, function(err, data) {
+      assert.ifError(err);
+    });
+    call.on('metadata', function(metadata) {
+      assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+      done();
+    });
+    var call2 = client.unary({}, function(err, data) {
+      assert.ifError(err);
+    });
+    call2.on('metadata', function(metadata) {
+      assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+      done();
+    });
+  });
+  describe('Per-rpc creds', function() {
+    var client;
+    var updater_creds;
+    before(function() {
+      client = new Client('localhost:' + port, client_ssl_creds,
+                          client_options);
+      var metadataUpdater = function(service_url, callback) {
+        var metadata = new grpc.Metadata();
+        metadata.set('plugin_key', 'plugin_value');
+        callback(null, metadata);
+      };
+      updater_creds = grpc.credentials.createFromMetadataGenerator(
+          metadataUpdater);
+    });
+    it('Should update metadata on a unary call', function(done) {
+      var call = client.unary({}, function(err, data) {
+        assert.ifError(err);
+      }, null, {credentials: updater_creds});
+      call.on('metadata', function(metadata) {
+        assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+        done();
+      });
+    });
+    it('should update metadata on a client streaming call', function(done) {
+      var call = client.clientStream(function(err, data) {
+        assert.ifError(err);
+      }, null, {credentials: updater_creds});
+      call.on('metadata', function(metadata) {
+        assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+        done();
+      });
+      call.end();
+    });
+    it('should update metadata on a server streaming call', function(done) {
+      var call = client.serverStream({}, null, {credentials: updater_creds});
+      call.on('data', function() {});
+      call.on('metadata', function(metadata) {
+        assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+        done();
+      });
+    });
+    it('should update metadata on a bidi streaming call', function(done) {
+      var call = client.bidiStream(null, {credentials: updater_creds});
+      call.on('data', function() {});
+      call.on('metadata', function(metadata) {
+        assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+        done();
+      });
+      call.end();
+    });
+    it('should be able to use multiple plugin credentials', function(done) {
+      var altMetadataUpdater = function(service_url, callback) {
+        var metadata = new grpc.Metadata();
+        metadata.set('other_plugin_key', 'other_plugin_value');
+        callback(null, metadata);
+      };
+      var alt_updater_creds = grpc.credentials.createFromMetadataGenerator(
+          altMetadataUpdater);
+      var combined_updater = grpc.credentials.combineCallCredentials(
+          updater_creds, alt_updater_creds);
+      var call = client.unary({}, function(err, data) {
+        assert.ifError(err);
+      }, null, {credentials: updater_creds});
+      call.on('metadata', function(metadata) {
+        assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
+        assert.deepEqual(metadata.get('other_plugin_key'),
+                         ['other_plugin_value']);
+        done();
+      });
+    });
+  });
+});
diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js
index 813221d..0f6c594 100644
--- a/src/node/test/end_to_end_test.js
+++ b/src/node/test/end_to_end_test.js
@@ -57,7 +57,7 @@
   };
 }
 
-var insecureCreds = grpc.Credentials.createInsecure();
+var insecureCreds = grpc.ChannelCredentials.createInsecure();
 
 describe('end-to-end', function() {
   var server;
diff --git a/src/node/test/health_test.js b/src/node/test/health_test.js
index 9267bff..a4dc24c 100644
--- a/src/node/test/health_test.js
+++ b/src/node/test/health_test.js
@@ -54,7 +54,7 @@
                                      grpc.ServerCredentials.createInsecure());
     healthServer.start();
     healthClient = new health.Client('localhost:' + port_num,
-                                     grpc.Credentials.createInsecure());
+                                     grpc.credentials.createInsecure());
   });
   after(function() {
     healthServer.forceShutdown();
diff --git a/src/node/test/math_client_test.js b/src/node/test/math_client_test.js
index 6a6607e..ea76777 100644
--- a/src/node/test/math_client_test.js
+++ b/src/node/test/math_client_test.js
@@ -56,7 +56,7 @@
                                grpc.ServerCredentials.createInsecure());
     server.start();
     math_client = new math.Math('localhost:' + port_num,
-                                grpc.Credentials.createInsecure());
+                                grpc.credentials.createInsecure());
     done();
   });
   after(function() {
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index c6c0613..ecbe0df 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -163,7 +163,7 @@
     Client = surface_client.makeProtobufClientConstructor(mathService);
   });
   beforeEach(function() {
-    client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+    client = new Client('localhost:' + port, grpc.credentials.createInsecure());
   });
   after(function() {
     server.forceShutdown();
@@ -217,7 +217,7 @@
     });
     var port = server.bind('localhost:0', server_insecure_creds);
     var Client = surface_client.makeProtobufClientConstructor(echo_service);
-    client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+    client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
   after(function() {
@@ -263,7 +263,7 @@
       server.start();
       var Client = grpc.makeGenericClientConstructor(string_service_attrs);
       client = new Client('localhost:' + port,
-                          grpc.Credentials.createInsecure());
+                          grpc.credentials.createInsecure());
     });
     after(function() {
       server.forceShutdown();
@@ -311,7 +311,7 @@
     });
     var port = server.bind('localhost:0', server_insecure_creds);
     var Client = surface_client.makeProtobufClientConstructor(test_service);
-    client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+    client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
     metadata = new grpc.Metadata();
     metadata.set('key', 'value');
@@ -437,7 +437,7 @@
     });
     port = server.bind('localhost:0', server_insecure_creds);
     Client = surface_client.makeProtobufClientConstructor(test_service);
-    client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+    client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
   after(function() {
@@ -484,7 +484,7 @@
       var Client = surface_client.makeClientConstructor(test_service_attrs,
                                                         'TestService');
       misbehavingClient = new Client('localhost:' + port,
-                                     grpc.Credentials.createInsecure());
+                                     grpc.credentials.createInsecure());
     });
     it('should respond correctly to a unary call', function(done) {
       misbehavingClient.unary(badArg, function(err, data) {
@@ -711,7 +711,7 @@
     });
     var port = server.bind('localhost:0', server_insecure_creds);
     Client = surface_client.makeProtobufClientConstructor(test_service);
-    client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+    client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
   after(function() {
@@ -748,7 +748,7 @@
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
-                                    grpc.Credentials.createInsecure());
+                                    grpc.credentials.createInsecure());
       var call = proxy_client.unary({}, function(err, value) {
         done();
       });
@@ -771,7 +771,7 @@
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
-                                    grpc.Credentials.createInsecure());
+                                    grpc.credentials.createInsecure());
       var call = proxy_client.clientStream(function(err, value) {
         done();
       });
@@ -792,7 +792,7 @@
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
-                                    grpc.Credentials.createInsecure());
+                                    grpc.credentials.createInsecure());
       var call = proxy_client.serverStream({});
       call.on('error', function(err) {
         done();
@@ -813,7 +813,7 @@
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
-                                    grpc.Credentials.createInsecure());
+                                    grpc.credentials.createInsecure());
       var call = proxy_client.bidiStream();
       call.on('error', function(err) {
         done();
@@ -842,7 +842,7 @@
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
-                                    grpc.Credentials.createInsecure());
+                                    grpc.credentials.createInsecure());
       var deadline = new Date();
       deadline.setSeconds(deadline.getSeconds() + 1);
       proxy_client.clientStream(function(err, value) {
@@ -865,7 +865,7 @@
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
-                                    grpc.Credentials.createInsecure());
+                                    grpc.credentials.createInsecure());
       var deadline = new Date();
       deadline.setSeconds(deadline.getSeconds() + 1);
       var call = proxy_client.bidiStream(null, {deadline: deadline});
@@ -888,7 +888,7 @@
     });
     var port = server.bind('localhost:0', server_insecure_creds);
     var Client = surface_client.makeProtobufClientConstructor(mathService);
-    client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+    client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
   after(function() {