You can hook into authentication success by passing an authentication_callback
to the client.
If you're happy relying on an internal API call, you can make sure your token is fresh by calling:
restforce_client.authenticate!
Typical client instantiation looks like this:
client = Restforce.new :oauth_token => 'oauth token', :instance_url => 'instance url'
Any method to interact with Salesforce (query
, find
, search
, etc.) generally wrap around api_get
:
def query(soql)
response = api_get 'query', :q => soql
mashify? ? response.body : response.body['records']
end
api_get
is defined in Restforce::Concerns::Verbs
:
def define_api_verb(verb)
define_method :"api_#{verb}" do |*args, &block|
args[0] = api_path(args[0])
send(verb, *args, &block)
end
end
send
is calling get
, which is defined in the same module:
def define_verb(verb)
define_method verb do |*args, &block|
retries = options[:authentication_retries]
begin
connection.send(verb, *args, &block)
rescue Restforce::UnauthorizedError
if retries > 0
retries -= 1
connection.url_prefix = options[:instance_url]
retry
end
raise
end
end
end
connection
comes from Restforce::Concerns::Connection
, and is essentially just a Faraday connection automatically configured with a bunch of Middleware:
def connection
@connection ||= Faraday.new(options[:instance_url], connection_options) do |builder|
...
# Handles reauthentication for 403 responses.
builder.use authentication_middleware, self, options if authentication_middleware
# Sets the oauth token in the headers.
builder.use Restforce::Middleware::Authorization, self, options
...
# Raises errors for 40x responses.
builder.use Restforce::Middleware::RaiseError
...
end
end
authentication_middleware
is a module that's determined at run time:
def authentication_middleware
if username_password?
Restforce::Middleware::Authentication::Password
elsif oauth_refresh?
Restforce::Middleware::Authentication::Token
end
end
Let's look at the middleware.
This simply adds the OAuth
header to each request:
def call(env)
env[:request_headers][AUTH_HEADER] = %(OAuth #{token})
@app.call(env)
end
def token
@options[:oauth_token]
end
This is a type of Faraday::Response
middleware (it acts after a response has been made).
def on_complete(env)
@env = env
case env[:status]
when 404
raise Faraday::Error::ResourceNotFound, message
when 401
raise Restforce::UnauthorizedError, message
when 413
raise Faraday::Error::ClientError.new("HTTP 413 - Request Entity Too Large", env[:response])
when 400...600
raise Faraday::Error::ClientError.new(message, env[:response])
end
end
The key part to note here is that a Restforce::UnauthorizedError
will be raised if a 401 response is returned.
This is an almost empty class, that just defines a params
method:
def params
{ :grant_type => 'refresh_token',
:refresh_token => @options[:refresh_token],
:client_id => @options[:client_id],
:client_secret => @options[:client_secret] }
end
It inherits from Restforce::Middleware::Authentication
, which works like this:
def call(env)
@app.call(env)
rescue Restforce::UnauthorizedError
authenticate!
raise
end
def authenticate!
response = connection.post '/services/oauth2/token' do |req|
req.body = encode_www_form(params)
end
raise Restforce::AuthenticationError, error_message(response) if response.status != 200
@options[:instance_url] = response.body['instance_url']
@options[:oauth_token] = response.body['access_token']
@options[:authentication_callback].call(response.body) if @options[:authentication_callback]
response.body
end
Interestingly, connection
in this case refers to a separate Faraday::Connection
than we saw previously.
The reraising in the rescue
block is for the verb method back in Restforce::Concerns::Verbs
:
...
rescue Restforce::UnauthorizedError
if retries > 0
retries -= 1
connection.url_prefix = options[:instance_url]
retry
end
raise
end
...