diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index 3938e0c436b2b3d3b88c9a532cdbf352006b9a2d..0eb8f48d8431316790c3d1a309a4bcc7f67ebf8f 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
   s.add_dependency 'faraday', '~> 0.9'
   s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.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 'signet', '~> 0.6.0'
   s.add_dependency 'xray', '~> 1.1'
diff --git a/src/ruby/lib/grpc/auth/signet.rb b/src/ruby/lib/grpc/auth/signet.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9cc51b7b3c5ab3b12f743ef9aef02250b29b1110
--- /dev/null
+++ b/src/ruby/lib/grpc/auth/signet.rb
@@ -0,0 +1,59 @@
+# 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
+  module OAuth2
+    # Google::RPC 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'] = 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
+    end
+  end
+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..d658e5c5448314ad85b89ad0c4798b79012cd037
--- /dev/null
+++ b/src/ruby/spec/auth/signet_spec.rb
@@ -0,0 +1,171 @@
+# 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 'spec_helper'
+
+require 'grpc/auth/signet'
+require 'openssl'
+require 'jwt'
+
+def build_json_response(payload)
+  [200,
+   { 'Content-Type' => 'application/json; charset=utf-8' },
+   MultiJson.dump(payload)]
+end
+
+describe Signet::OAuth2::Client do
+  describe 'when using RSA keys' do
+    before 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_oauth_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
+
+    describe '#fetch_access_token' do
+      it 'should set access_token to the fetched value' do
+        token = '1/abcdef1234567890'
+        stubs = make_oauth_stubs with_access_token: token
+        c = Faraday.new(url: 'https://www.google.com') 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_oauth_stubs with_access_token: token
+        c = Faraday.new(url: 'https://www.google.com') do |b|
+          b.adapter(:test, stubs)
+        end
+
+        md = { foo: 'bar' }
+        @client.apply!(md, connection: c)
+        want = { :foo => 'bar', 'auth' => token }
+        expect(md).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_oauth_stubs with_access_token: token
+        c = Faraday.new(url: 'https://www.google.com') 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_oauth_stubs with_access_token: token
+        c = Faraday.new(url: 'https://www.google.com') do |b|
+          b.adapter(:test, stubs)
+        end
+
+        md = { foo: 'bar' }
+        got = @client.apply(md, connection: c)
+        want = { :foo => 'bar', 'auth' => 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_oauth_stubs with_access_token: token
+        c = Faraday.new(url: 'https://www.google.com') 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', 'auth' => 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_oauth_stubs with_access_token: t
+          c = Faraday.new(url: 'https://www.google.com') do |b|
+            b.adapter(:test, stubs)
+          end
+          md = { foo: 'bar' }
+          got = @client.apply(md, connection: c)
+          want = { :foo => 'bar', 'auth' => t }
+          expect(got).to eq(want)
+          stubs.verify_stubbed_calls
+          @client.expires_at -= 3601 # default is to expire in 1hr
+        end
+      end
+    end
+  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|