diff --git a/src/ruby/Rakefile b/src/ruby/Rakefile
index 5fc325ef0eb2b6f15ca53d3d8d4008bdc6a554cc..b27305d16c3abfa766e26ab2bcb8f942bc76ef0c 100755
--- a/src/ruby/Rakefile
+++ b/src/ruby/Rakefile
@@ -35,18 +35,20 @@ namespace :spec do
 
         t.pattern = spec_files
         t.rspec_opts = "--tag #{suite[:tag]}" if suite[:tag]
-        t.rspec_opts = suite[:tags].map{ |t| "--tag #{t}" }.join(' ') if suite[:tags]
+        if suite[:tags]
+          t.rspec_opts = suite[:tags].map { |x| "--tag #{x}" }.join(' ')
+        end
       end
     end
   end
 end
 
-desc 'Run compiles the extension, runs all the tests'
+desc 'Compiles the extension then runs all the tests'
 task :all
 
 task default: :all
-task 'spec:suite:wrapper' => :compile
+task 'spec:suite:wrapper' => [:compile, :rubocop]
 task 'spec:suite:idiomatic' => 'spec:suite:wrapper'
 task 'spec:suite:bidi' => 'spec:suite:wrapper'
 task 'spec:suite:server' => 'spec:suite:wrapper'
-task :all => ['spec:suite:idiomatic', 'spec:suite:bidi', 'spec:suite:server']
+task all: ['spec:suite:idiomatic', 'spec:suite:bidi', 'spec:suite:server']
diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb
index 86739b7b670b2b748fb9985c3b5a0c13abaf25b2..e29e22b8c151e35a502729c9c3655de8ff8beebd 100755
--- a/src/ruby/bin/interop/interop_client.rb
+++ b/src/ruby/bin/interop/interop_client.rb
@@ -56,6 +56,8 @@ require 'test/cpp/interop/empty'
 
 require 'signet/ssl_config'
 
+include Google::RPC::Auth
+
 # loads the certificates used to access the test server securely.
 def load_test_certs
   this_dir = File.expand_path(File.dirname(__FILE__))
@@ -67,40 +69,54 @@ end
 # loads the certificates used to access the test server securely.
 def load_prod_cert
   fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil?
-  p "loading prod certs from #{ENV['SSL_CERT_FILE']}"
+  logger.info("loading prod certs from #{ENV['SSL_CERT_FILE']}")
   File.open(ENV['SSL_CERT_FILE']).read
 end
 
-# creates a Credentials from the test certificates.
+# creates SSL Credentials from the test certificates.
 def test_creds
   certs = load_test_certs
   GRPC::Core::Credentials.new(certs[0])
 end
 
-RX_CERT = /-----BEGIN CERTIFICATE-----\n.*?-----END CERTIFICATE-----\n/m
-
-
-# creates a Credentials from the production certificates.
+# creates SSL Credentials from the production certificates.
 def prod_creds
   cert_text = load_prod_cert
   GRPC::Core::Credentials.new(cert_text)
 end
 
-# creates a test stub that accesses host:port securely.
-def create_stub(host, port, is_secure, host_override, use_test_ca)
-  address = "#{host}:#{port}"
-  if is_secure
-    creds = nil
-    if use_test_ca
-      creds = test_creds
-    else
-      creds = prod_creds
-    end
+# creates the SSL Credentials.
+def ssl_creds(use_test_ca)
+  return test_creds if use_test_ca
+  prod_creds
+end
 
+# creates a test stub that accesses host:port securely.
+def create_stub(opts)
+  address = "#{opts.host}:#{opts.port}"
+  if opts.secure
     stub_opts = {
-      :creds => creds,
-      GRPC::Core::Channel::SSL_TARGET => host_override
+      :creds => ssl_creds(opts.use_test_ca),
+      GRPC::Core::Channel::SSL_TARGET => opts.host_override
     }
+
+    # Add service account creds if specified
+    if %w(all service_account_creds).include?(opts.test_case)
+      unless opts.oauth_scope.nil?
+        fd = StringIO.new(File.read(opts.oauth_key_file))
+        logger.info("loading oauth certs from #{opts.oauth_key_file}")
+        auth_creds = ServiceAccountCredentials.new(opts.oauth_scope, fd)
+        stub_opts[:update_metadata] = auth_creds.updater_proc
+      end
+    end
+
+    # Add compute engine creds if specified
+    if %w(all compute_engine_creds).include?(opts.test_case)
+      unless opts.oauth_scope.nil?
+        stub_opts[:update_metadata] = GCECredentials.new.update_proc
+      end
+    end
+
     logger.info("... connecting securely to #{address}")
     Grpc::Testing::TestService::Stub.new(address, **stub_opts)
   else
@@ -158,9 +174,10 @@ class NamedTests
   include Grpc::Testing::PayloadType
   attr_accessor :assertions # required by Minitest::Assertions
 
-  def initialize(stub)
+  def initialize(stub, args)
     @assertions = 0  # required by Minitest::Assertions
     @stub = stub
+    @args = args
   end
 
   def empty_unary
@@ -170,21 +187,37 @@ class NamedTests
   end
 
   def large_unary
-    req_size, wanted_response_size = 271_828, 314_159
-    payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
-    req = SimpleRequest.new(response_type: :COMPRESSABLE,
-                            response_size: wanted_response_size,
-                            payload: payload)
-    resp = @stub.unary_call(req)
-    assert_equal(:COMPRESSABLE, resp.payload.type,
-                 'large_unary: payload had the wrong type')
-    assert_equal(wanted_response_size, resp.payload.body.length,
-                 'large_unary: payload had the wrong length')
-    assert_equal(nulls(wanted_response_size), resp.payload.body,
-                 'large_unary: payload content is invalid')
+    perform_large_unary
     p 'OK: large_unary'
   end
 
+  def service_account_creds
+    # ignore this test if the oauth options are not set
+    if @args.oauth_scope.nil? || @args.oauth_key_file.nil?
+      p 'NOT RUN: service_account_creds; no service_account settings'
+      return
+    end
+    json_key = File.read(@args.oauth_key_file)
+    wanted_email = MultiJson.load(json_key)['client_email']
+    resp = perform_large_unary(fill_username: true,
+                               fill_oauth_scope: true)
+    assert_equal(wanted_email, resp.username,
+                 'service_account_creds: incorrect username')
+    assert(@args.oauth_scope.include?(resp.oauth_scope),
+           'service_account_creds: incorrect oauth_scope')
+    p 'OK: service_account_creds'
+  end
+
+  def compute_engine_creds
+    resp = perform_large_unary(fill_username: true,
+                               fill_oauth_scope: true)
+    assert(@args.oauth_scope.include?(resp.oauth_scope),
+           'service_account_creds: incorrect oauth_scope')
+    assert_equal(@args.default_service_account, resp.username,
+                 'service_account_creds: incorrect username')
+    p 'OK: compute_engine_creds'
+  end
+
   def client_streaming
     msg_sizes = [27_182, 8, 1828, 45_904]
     wanted_aggregate_size = 74_922
@@ -230,64 +263,89 @@ class NamedTests
       method(m).call
     end
   end
+
+  private
+
+  def perform_large_unary(fill_username: false, fill_oauth_scope: false)
+    req_size, wanted_response_size = 271_828, 314_159
+    payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
+    req = SimpleRequest.new(response_type: :COMPRESSABLE,
+                            response_size: wanted_response_size,
+                            payload: payload)
+    req.fill_username = fill_username
+    req.fill_oauth_scope = fill_oauth_scope
+    resp = @stub.unary_call(req)
+    assert_equal(:COMPRESSABLE, resp.payload.type,
+                 'large_unary: payload had the wrong type')
+    assert_equal(wanted_response_size, resp.payload.body.length,
+                 'large_unary: payload had the wrong length')
+    assert_equal(nulls(wanted_response_size), resp.payload.body,
+                 'large_unary: payload content is invalid')
+    resp
+  end
 end
 
+# Args is used to hold the command line info.
+Args = Struct.new(:default_service_account, :host, :host_override,
+                  :oauth_scope, :oauth_key_file, :port, :secure, :test_case,
+                  :use_test_ca)
+
 # validates the the command line options, returning them as a Hash.
-def parse_options
-  options = {
-    'secure' => false,
-    'server_host' => nil,
-    'server_host_override' => nil,
-    'server_port' => nil,
-    'test_case' => nil
-  }
+def parse_args
+  args = Args.new
+  args.host_override = 'foo.test.google.com'
   OptionParser.new do |opts|
-    opts.banner = 'Usage: --server_host <server_host> --server_port server_port'
+    opts.on('--oauth_scope scope',
+            'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
     opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
-      options['server_host'] = v
+      args['host'] = v
+    end
+    opts.on('--default_service_account email_address',
+            'email address of the default service account') do |v|
+      args['default_service_account'] = v
+    end
+    opts.on('--service_account_key_file PATH',
+            'Path to the service account json key file') do |v|
+      args['oauth_key_file'] = v
     end
     opts.on('--server_host_override HOST_OVERRIDE',
             'override host via a HTTP header') do |v|
-      options['server_host_override'] = v
-    end
-    opts.on('--server_port SERVER_PORT', 'server port') do |v|
-      options['server_port'] = v
+      args['host_override'] = v
     end
+    opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v }
     # instance_methods(false) gives only the methods defined in that class
     test_cases = NamedTests.instance_methods(false).map(&:to_s)
     test_case_list = test_cases.join(',')
     opts.on('--test_case CODE', test_cases, {}, 'select a test_case',
-            "  (#{test_case_list})") do |v|
-      options['test_case'] = v
-    end
+            "  (#{test_case_list})") { |v| args['test_case'] = v }
     opts.on('-s', '--use_tls', 'require a secure connection?') do |v|
-      options['secure'] = v
+      args['secure'] = v
     end
     opts.on('-t', '--use_test_ca',
             'if secure, use the test certificate?') do |v|
-      options['use_test_ca'] = v
+      args['use_test_ca'] = v
     end
   end.parse!
-  _check_options(options)
+  _check_args(args)
 end
 
-def _check_options(opts)
-  %w(server_host server_port test_case).each do |arg|
-    if opts[arg].nil?
+def _check_args(args)
+  %w(host port test_case).each do |a|
+    if args[a].nil?
       fail(OptionParser::MissingArgument, "please specify --#{arg}")
     end
   end
-  if opts['server_host_override'].nil?
-    opts['server_host_override'] = opts['server_host']
+  if args['oauth_key_file'].nil? ^ args['oauth_scope'].nil?
+    fail(OptionParser::MissingArgument,
+         'please specify both of --service_account_key_file and --oauth_scope')
   end
-  opts
+  args
 end
 
 def main
-  opts = parse_options
-  stub = create_stub(opts['server_host'], opts['server_port'], opts['secure'],
-                     opts['server_host_override'], opts['use_test_ca'])
-  NamedTests.new(stub).method(opts['test_case']).call
+  opts = parse_args
+  stub = create_stub(opts)
+  NamedTests.new(stub, opts).method(opts['test_case']).call
 end
 
 main
diff --git a/src/ruby/bin/interop/test/cpp/interop/messages.rb b/src/ruby/bin/interop/test/cpp/interop/messages.rb
index 491608bff263ca34cded356bf587460f33ba3b51..b86cd396a9f9a3e704653924677018b0e9eb9365 100644
--- a/src/ruby/bin/interop/test/cpp/interop/messages.rb
+++ b/src/ruby/bin/interop/test/cpp/interop/messages.rb
@@ -41,10 +41,13 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
     optional :response_type, :enum, 1, "grpc.testing.PayloadType"
     optional :response_size, :int32, 2
     optional :payload, :message, 3, "grpc.testing.Payload"
+    optional :fill_username, :bool, 4
+    optional :fill_oauth_scope, :bool, 5
   end
   add_message "grpc.testing.SimpleResponse" do
     optional :payload, :message, 1, "grpc.testing.Payload"
-    optional :effective_gaia_user_id, :int64, 2
+    optional :username, :string, 2
+    optional :oauth_scope, :string, 3
   end
   add_message "grpc.testing.StreamingInputCallRequest" do
     optional :payload, :message, 1, "grpc.testing.Payload"
diff --git a/src/ruby/bin/math.rb b/src/ruby/bin/math.rb
old mode 100644
new mode 100755
diff --git a/src/ruby/bin/math_services.rb b/src/ruby/bin/math_services.rb
old mode 100644
new mode 100755
diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index ffd084dc9128e7cc091291edb4cb0d55a0af5217..2ce242dd0b11de1035dbfdc29f09f9236e54de52 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -1,3 +1,4 @@
+# -*- ruby -*-
 # encoding: utf-8
 $LOAD_PATH.push File.expand_path('../lib', __FILE__)
 require 'grpc/version'
@@ -19,11 +20,14 @@ Gem::Specification.new do |s|
   s.require_paths = ['lib']
   s.platform      = Gem::Platform::RUBY
 
-  s.add_dependency 'xray'
-  s.add_dependency 'logging', '~> 1.8'
+  s.add_dependency 'faraday', '~> 0.9'
   s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'
-  s.add_dependency 'signet', '~> 0.5.1'
+  s.add_dependency 'logging', '~> 1.8'
+  s.add_dependency 'jwt', '~> 1.2.1'
   s.add_dependency 'minitest', '~> 5.4'  # reqd for interop tests
+  s.add_dependency 'multijson', '1.10.1'
+  s.add_dependency 'signet', '~> 0.6.0'
+  s.add_dependency 'xray', '~> 1.1'
 
   s.add_development_dependency 'bundler', '~> 1.7'
   s.add_development_dependency 'rake', '~> 10.0'
diff --git a/src/ruby/lib/grpc.rb b/src/ruby/lib/grpc.rb
index 81c67ec8592c7e664dee23bced427bace59c86a2..758ac0c2d1662d4cec01321afef76a80e6a062f8 100644
--- a/src/ruby/lib/grpc.rb
+++ b/src/ruby/lib/grpc.rb
@@ -27,6 +27,8 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+require 'grpc/auth/compute_engine.rb'
+require 'grpc/auth/service_account.rb'
 require 'grpc/errors'
 require 'grpc/grpc'
 require 'grpc/logconfig'
diff --git a/src/ruby/lib/grpc/auth/compute_engine.rb b/src/ruby/lib/grpc/auth/compute_engine.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9004bef46e5d8e0b44bffbcd2db03d46b81cf525
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/compute_engine.rb
@@ -0,0 +1,69 @@
+# 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.
+
+require 'faraday'
+require 'grpc/auth/signet'
+
+module Google
+  module RPC
+    # Module Auth provides classes that provide Google-specific authentication
+    # used to access Google gRPC services.
+    module Auth
+      # Extends Signet::OAuth2::Client so that the auth token is obtained from
+      # the GCE metadata server.
+      class GCECredentials < Signet::OAuth2::Client
+        COMPUTE_AUTH_TOKEN_URI = 'http://metadata/computeMetadata/v1/'\
+                                 'instance/service-accounts/default/token'
+        COMPUTE_CHECK_URI = 'http://metadata.google.internal'
+
+        # Detect if this appear to be a GCE instance, by checking if metadata
+        # is available
+        def self.on_gce?(options = {})
+          c = options[:connection] || Faraday.default_connection
+          resp = c.get(COMPUTE_CHECK_URI)
+          return false unless resp.status == 200
+          return false unless resp.headers.key?('Metadata-Flavor')
+          return resp.headers['Metadata-Flavor'] == 'Google'
+        rescue Faraday::ConnectionFailed
+          return false
+        end
+
+        # Overrides the super class method to change how access tokens are
+        # fetched.
+        def fetch_access_token(options = {})
+          c = options[:connection] || Faraday.default_connection
+          c.headers = { 'Metadata-Flavor' => 'Google' }
+          resp = c.get(COMPUTE_AUTH_TOKEN_URI)
+          Signet::OAuth2.parse_credentials(resp.body,
+                                           resp.headers['content-type'])
+        end
+      end
+    end
+  end
+end
diff --git a/src/ruby/lib/grpc/auth/service_account.rb b/src/ruby/lib/grpc/auth/service_account.rb
new file mode 100644
index 0000000000000000000000000000000000000000..35b5cbfe2de1ff5eff7ee66719ddc586cf661988
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/service_account.rb
@@ -0,0 +1,68 @@
+# 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.
+
+require 'grpc/auth/signet'
+require 'multi_json'
+require 'openssl'
+
+# Reads the private key and client email fields from service account JSON key.
+def read_json_key(json_key_io)
+  json_key = MultiJson.load(json_key_io.read)
+  fail 'missing client_email' unless json_key.key?('client_email')
+  fail 'missing private_key' unless json_key.key?('private_key')
+  [json_key['private_key'], json_key['client_email']]
+end
+
+module Google
+  module RPC
+    # Module Auth provides classes that provide Google-specific authentication
+    # used to access Google gRPC services.
+    module Auth
+      # Authenticates requests using Google's Service Account credentials.
+      # (cf https://developers.google.com/accounts/docs/OAuth2ServiceAccount)
+      class ServiceAccountCredentials < Signet::OAuth2::Client
+        TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token'
+        AUDIENCE = TOKEN_CRED_URI
+
+        # Initializes a ServiceAccountCredentials.
+        #
+        # @param scope [string|array] the scope(s) to access
+        # @param json_key_io [IO] an IO from which the JSON key can be read
+        def initialize(scope, json_key_io)
+          private_key, client_email = read_json_key(json_key_io)
+          super(token_credential_uri: TOKEN_CRED_URI,
+                audience: AUDIENCE,
+                scope: scope,
+                issuer: client_email,
+                signing_key: OpenSSL::PKey::RSA.new(private_key))
+        end
+      end
+    end
+  end
+end
diff --git a/src/ruby/lib/grpc/auth/signet.rb b/src/ruby/lib/grpc/auth/signet.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a8bce1255c239b8463e610ec9ec7a46cb8b32171
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/signet.rb
@@ -0,0 +1,67 @@
+# 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.
+
+require 'signet/oauth_2/client'
+
+module Signet
+  # Signet::OAuth2 supports OAuth2 authentication.
+  module OAuth2
+    AUTH_METADATA_KEY = :Authorization
+    # Signet::OAuth2::Client creates an OAuth2 client
+    #
+    # Here client is re-opened to add the #apply and #apply! methods which
+    # update a hash map with the fetched authentication token
+    #
+    # Eventually, this change may be merged into signet itself, or some other
+    # package that provides Google-specific auth via signet, and this extension
+    # will be unnecessary.
+    class Client
+      # Updates a_hash updated with the authentication token
+      def apply!(a_hash, opts = {})
+        # fetch the access token there is currently not one, or if the client
+        # has expired
+        fetch_access_token!(opts) if access_token.nil? || expired?
+        a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
+      end
+
+      # Returns a clone of a_hash updated with the authentication token
+      def apply(a_hash, opts = {})
+        a_copy = a_hash.clone
+        apply!(a_copy, opts)
+        a_copy
+      end
+
+      # Returns a reference to the #apply method, suitable for passing as
+      # a closure
+      def updater_proc
+        lambda(&method(:apply))
+      end
+    end
+  end
+end
diff --git a/src/ruby/spec/auth/apply_auth_examples.rb b/src/ruby/spec/auth/apply_auth_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..09b393026f02f888c13e3d5ff13492e28d8d6d06
--- /dev/null
+++ b/src/ruby/spec/auth/apply_auth_examples.rb
@@ -0,0 +1,163 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'faraday'
+require 'spec_helper'
+
+def build_json_response(payload)
+  [200,
+   { 'Content-Type' => 'application/json; charset=utf-8' },
+   MultiJson.dump(payload)]
+end
+
+WANTED_AUTH_KEY = :Authorization
+
+shared_examples 'apply/apply! are OK' do
+  # tests that use these examples need to define
+  #
+  # @client which should be an auth client
+  #
+  # @make_auth_stubs, which should stub out the expected http behaviour of the
+  # auth client
+  describe '#fetch_access_token' do
+    it 'should set access_token to the fetched value' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      @client.fetch_access_token!(connection: c)
+      expect(@client.access_token).to eq(token)
+      stubs.verify_stubbed_calls
+    end
+  end
+
+  describe '#apply!' do
+    it 'should update the target hash with fetched access token' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      @client.apply!(md, connection: c)
+      want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+      expect(md).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+  end
+
+  describe 'updater_proc' do
+    it 'should provide a proc that updates a hash with the access token' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      the_proc = @client.updater_proc
+      got = the_proc.call(md, connection: c)
+      want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+      expect(got).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+  end
+
+  describe '#apply' do
+    it 'should not update the original hash with the access token' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      @client.apply(md, connection: c)
+      want = { foo: 'bar' }
+      expect(md).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should add the token to the returned hash' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      md = { foo: 'bar' }
+      got = @client.apply(md, connection: c)
+      want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+      expect(got).to eq(want)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should not fetch a new token if the current is not expired' do
+      token = '1/abcdef1234567890'
+      stubs = make_auth_stubs with_access_token: token
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+
+      n = 5 # arbitrary
+      n.times do |_t|
+        md = { foo: 'bar' }
+        got = @client.apply(md, connection: c)
+        want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
+        expect(got).to eq(want)
+      end
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should fetch a new token if the current one is expired' do
+      token_1 = '1/abcdef1234567890'
+      token_2 = '2/abcdef1234567890'
+
+      [token_1, token_2].each do |t|
+        stubs = make_auth_stubs with_access_token: t
+        c = Faraday.new do |b|
+          b.adapter(:test, stubs)
+        end
+        md = { foo: 'bar' }
+        got = @client.apply(md, connection: c)
+        want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{t}" }
+        expect(got).to eq(want)
+        stubs.verify_stubbed_calls
+        @client.expires_at -= 3601 # default is to expire in 1hr
+      end
+    end
+  end
+end
diff --git a/src/ruby/spec/auth/compute_engine_spec.rb b/src/ruby/spec/auth/compute_engine_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e0b4660fa5443b95197c4c7ad431e4e05a42642
--- /dev/null
+++ b/src/ruby/spec/auth/compute_engine_spec.rb
@@ -0,0 +1,108 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'apply_auth_examples'
+require 'faraday'
+require 'grpc/auth/compute_engine'
+require 'spec_helper'
+
+describe Google::RPC::Auth::GCECredentials do
+  MD_URI = '/computeMetadata/v1/instance/service-accounts/default/token'
+  GCECredentials = Google::RPC::Auth::GCECredentials
+
+  before(:example) do
+    @client = GCECredentials.new
+  end
+
+  def make_auth_stubs(with_access_token: '')
+    Faraday::Adapter::Test::Stubs.new do |stub|
+      stub.get(MD_URI) do |env|
+        headers = env[:request_headers]
+        expect(headers['Metadata-Flavor']).to eq('Google')
+        build_json_response(
+            'access_token' => with_access_token,
+            'token_type' => 'Bearer',
+            'expires_in' => 3600)
+      end
+    end
+  end
+
+  it_behaves_like 'apply/apply! are OK'
+
+  describe '#on_gce?' do
+    it 'should be true when Metadata-Flavor is Google' do
+      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
+        stub.get('/') do |_env|
+          [200,
+           { 'Metadata-Flavor' => 'Google' },
+           '']
+        end
+      end
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+      expect(GCECredentials.on_gce?(connection: c)).to eq(true)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should be false when Metadata-Flavor is not Google' do
+      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
+        stub.get('/') do |_env|
+          [200,
+           { 'Metadata-Flavor' => 'NotGoogle' },
+           '']
+        end
+      end
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+      expect(GCECredentials.on_gce?(connection: c)).to eq(false)
+      stubs.verify_stubbed_calls
+    end
+
+    it 'should be false if the response is not 200' do
+      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
+        stub.get('/') do |_env|
+          [404,
+           { 'Metadata-Flavor' => 'Google' },
+           '']
+        end
+      end
+      c = Faraday.new do |b|
+        b.adapter(:test, stubs)
+      end
+      expect(GCECredentials.on_gce?(connection: c)).to eq(false)
+      stubs.verify_stubbed_calls
+    end
+  end
+end
diff --git a/src/ruby/spec/auth/service_account_spec.rb b/src/ruby/spec/auth/service_account_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cbc6a73ac20c245ecb98d8096e94820487a55fcd
--- /dev/null
+++ b/src/ruby/spec/auth/service_account_spec.rb
@@ -0,0 +1,75 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'apply_auth_examples'
+require 'grpc/auth/service_account'
+require 'jwt'
+require 'multi_json'
+require 'openssl'
+require 'spec_helper'
+
+describe Google::RPC::Auth::ServiceAccountCredentials do
+  before(:example) do
+    @key = OpenSSL::PKey::RSA.new(2048)
+    cred_json = {
+      private_key_id: 'a_private_key_id',
+      private_key: @key.to_pem,
+      client_email: 'app@developer.gserviceaccount.com',
+      client_id: 'app.apps.googleusercontent.com',
+      type: 'service_account'
+    }
+    cred_json_text = MultiJson.dump(cred_json)
+    @client = Google::RPC::Auth::ServiceAccountCredentials.new(
+        'https://www.googleapis.com/auth/userinfo.profile',
+        StringIO.new(cred_json_text))
+  end
+
+  def make_auth_stubs(with_access_token: '')
+    Faraday::Adapter::Test::Stubs.new do |stub|
+      stub.post('/oauth2/v3/token') do |env|
+        params = Addressable::URI.form_unencode(env[:body])
+        _claim, _header = JWT.decode(params.assoc('assertion').last,
+                                     @key.public_key)
+        want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer']
+        expect(params.assoc('grant_type')).to eq(want)
+        build_json_response(
+          'access_token' => with_access_token,
+          'token_type' => 'Bearer',
+          'expires_in' => 3600
+        )
+      end
+    end
+  end
+
+  it_behaves_like 'apply/apply! are OK'
+end
diff --git a/src/ruby/spec/auth/signet_spec.rb b/src/ruby/spec/auth/signet_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1712edf29614061a25fc15faa2b1e3ea45ef75d9
--- /dev/null
+++ b/src/ruby/spec/auth/signet_spec.rb
@@ -0,0 +1,70 @@
+# 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.
+
+spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.uniq!
+
+require 'apply_auth_examples'
+require 'grpc/auth/signet'
+require 'jwt'
+require 'openssl'
+require 'spec_helper'
+
+describe Signet::OAuth2::Client do
+  before(:example) do
+    @key = OpenSSL::PKey::RSA.new(2048)
+    @client = Signet::OAuth2::Client.new(
+        token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
+        scope: 'https://www.googleapis.com/auth/userinfo.profile',
+        issuer: 'app@example.com',
+        audience: 'https://accounts.google.com/o/oauth2/token',
+        signing_key: @key
+      )
+  end
+
+  def make_auth_stubs(with_access_token: '')
+    Faraday::Adapter::Test::Stubs.new do |stub|
+      stub.post('/o/oauth2/token') do |env|
+        params = Addressable::URI.form_unencode(env[:body])
+        _claim, _header = JWT.decode(params.assoc('assertion').last,
+                                     @key.public_key)
+        want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer']
+        expect(params.assoc('grant_type')).to eq(want)
+        build_json_response(
+          'access_token' => with_access_token,
+          'token_type' => 'Bearer',
+          'expires_in' => 3600
+        )
+      end
+    end
+  end
+
+  it_behaves_like 'apply/apply! are OK'
+end
diff --git a/src/ruby/spec/channel_spec.rb b/src/ruby/spec/channel_spec.rb
index 189d1c67ab2831011a4e3b2bf7497c5a6e839082..82c7915debb61c5dca9c67b9c2867e51cb2b75e6 100644
--- a/src/ruby/spec/channel_spec.rb
+++ b/src/ruby/spec/channel_spec.rb
@@ -29,8 +29,6 @@
 
 require 'grpc'
 
-FAKE_HOST='localhost:0'
-
 def load_test_certs
   test_root = File.join(File.dirname(__FILE__), 'testdata')
   files = ['ca.pem', 'server1.key', 'server1.pem']
@@ -38,6 +36,8 @@ def load_test_certs
 end
 
 describe GRPC::Core::Channel do
+  FAKE_HOST = 'localhost:0'
+
   def create_test_cert
     GRPC::Core::Credentials.new(load_test_certs[0])
   end
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
index e81b2168b0bed9fea365b4488988cdd5c69b215e..599e68bef032c53e459c9b6d6c387a77434d4e16 100644
--- a/src/ruby/spec/generic/active_call_spec.rb
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -371,6 +371,6 @@ describe GRPC::ActiveCall do
   end
 
   def deadline
-    Time.now + 0.25  # in 0.25 seconds; arbitrary
+    Time.now + 1  # in 1 second; arbitrary
   end
 end
diff --git a/src/ruby/spec/spec_helper.rb b/src/ruby/spec/spec_helper.rb
index 3322674e97c667213a9f7d3d986a07b47bfc1f64..ea0a256713183944d7fe90b8d6dc50818bc759ca 100644
--- a/src/ruby/spec/spec_helper.rb
+++ b/src/ruby/spec/spec_helper.rb
@@ -27,10 +27,22 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+spec_dir = File.expand_path(File.dirname(__FILE__))
+root_dir = File.expand_path(File.join(spec_dir, '..'))
+lib_dir = File.expand_path(File.join(root_dir, 'lib'))
+
+$LOAD_PATH.unshift(spec_dir)
+$LOAD_PATH.unshift(lib_dir)
+$LOAD_PATH.uniq!
+
+require 'faraday'
 require 'rspec'
 require 'logging'
 require 'rspec/logging_helper'
 
+# Allow Faraday to support test stubs
+Faraday::Adapter.load_middleware(:test)
+
 # Configure RSpec to capture log messages for each test. The output from the
 # logs will be stored in the @log_output variable. It is a StringIO instance.
 RSpec.configure do |config|