## Summary 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: ```ruby client.authenticate! ``` ## Walkthrough Typical client instantiation looks like this: ```ruby 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`: ```ruby def query(soql) response = api_get 'query', :q => soql mashify? ? response.body : response.body['records'] end ``` `api_get` is defined in [`Restforce::Concerns::Verbs`](https://github.com/ejholmes/restforce/blob/92b2a0041984d3b4b058e2b4ee337a375056b598/lib/restforce/concerns/verbs.rb): ```ruby 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: ```ruby 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`](https://github.com/ejholmes/restforce/blob/92b2a0041984d3b4b058e2b4ee337a375056b598/lib/restforce/concerns/connection.rb), and is essentially just a Faraday connection automatically configured with a bunch of Middleware: ```ruby 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: ```ruby 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. #### [Restforce::Middleware::Authorization](https://github.com/ejholmes/restforce/blob/92b2a0041984d3b4b058e2b4ee337a375056b598/lib/restforce/middleware/authorization.rb) This simply adds the `OAuth` header to each request: ```ruby def call(env) env[:request_headers][AUTH_HEADER] = %(OAuth #{token}) @app.call(env) end def token @options[:oauth_token] end ``` #### Restforce::Middleware::RaiseError This is a type of `Faraday::Response` middleware (it acts after a response has been made). ```ruby 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. #### Restforce::Middleware::Authentication::Token This is an almost empty class, that just defines a `params` method: ```ruby 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: ```ruby 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`: ```ruby ... rescue Restforce::UnauthorizedError if retries > 0 retries -= 1 connection.url_prefix = options[:instance_url] retry end raise end ... ```