chulkilee / ex_force

A Salesforce REST API wrapper for Elixir
https://hex.pm/packages/ex_force
MIT License
38 stars 27 forks source link

Supported oauth2 authorization grant types? #1

Closed epinault closed 6 years ago

epinault commented 6 years ago

do you support username-password flow only? Or do you also support web server flow?

chulkilee commented 6 years ago

Hi, thanks for the first github issue!

Authorization grant flow (or "web server flow" as in salesforce doc) requires callback phase, which is out of scope of this project, which is for very thin layer.

Once you get an access token after the oauth dance, you can just build new ExForce.Config and pass it to all functions, to call Salesforce API on the behalf of the authenticated user.

If I need the feature, I'd write a oauth2 library wrapper for salesforce oauth, and then write a ueberauth strategy with it.

epinault commented 6 years ago

I am less concern I guess about the web server flow really than just being able to connect using the Oauth Token ( have have the flow down out side of this library). But then I could not figure out how to pass the Oauth token in the config. I ll try that file you have to see if I can get it to work.

I ll take a look at the Oauth2 Lib so I can support refresh token too but I agree that it might not be needed in this lib . thanks for the prompt reply ! this lib looks very promising!

chulkilee commented 6 years ago

Here is how you can use oauth2 library.

defmodule Test do
  def client_opts do
    [
      site: System.get_env("SALESFORCE_SITE"),
      client_id: System.get_env("SALESFORCE_CLIENT_ID"),
      client_secret: System.get_env("SALESFORCE_CLIENT_SECRET"),
      authorize_url: "/services/oauth2/authorize",
      token_url: "/services/oauth2/token"
    ]
  end

  def test_password do
    password_opts = [
      username: System.get_env("SALESFORCE_USERNAME"),
      password: System.get_env("SALESFORCE_PASSWORD") <> System.get_env("SALESFORCE_SECURITY_TOKEN")
    ]

    client =
      client_opts()
      |> Keyword.put(:strategy, OAuth2.Strategy.Password)
      |> OAuth2.Client.new()
      |> OAuth2.Client.put_header("Accept", "application/json")

    {:ok, client} = OAuth2.Client.get_token(client, password_opts)

    true = verify_signature(client)
    IO.inspect client
  end

  def test_auth_code do
    # put redirect uri as configured in Salesforce
    redirect_uri = "http://localhost:4000/auth/salesforce/callback"

    client =
      client_opts()
      |> Keyword.put(:strategy, OAuth2.Strategy.AuthCode)
      |> Keyword.put(:redirect_uri, redirect_uri)
      |> OAuth2.Client.new()
      |> OAuth2.Client.put_header("Accept", "application/json")

    # open this URL in browser
    IO.puts OAuth2.Client.authorize_url!(client)

    # put urldecoded auth code here
    auth_code = "..."

    client
    # when "Require Secret for Web Server Flow" option is checked
    |> OAuth2.Client.put_param(:client_secret, client.client_secret)
    |> OAuth2.Client.get_token(code: auth_code)
    |> IO.inspect
  end

  def test_refresh do
    # client must be configured with "Perform requests on your behalf at any time (refresh_token, offline_access)"
    client =
      client_opts()
      |> Keyword.put(:strategy, OAuth2.Strategy.Refresh)
      |> OAuth2.Client.new()
      |> OAuth2.Client.put_header("Accept", "application/json")

    refresh_token = "..."

    client
    # when "Require Secret for Web Server Flow" option is checked
    |> OAuth2.Client.put_param(:client_secret, client.client_secret)
    |> OAuth2.Client.get_token(refresh_token: refresh_token)
    |> IO.inspect
  end

  # Base64-encoded HMAC-SHA256 signature signed with the consumer's private key containing the concatenated ID and issued_at value. The signature can be used to verify that the identity URL wasn’t modified because it was sent by the server.
  def verify_signature(%OAuth2.Client{client_secret: client_secret, token: %OAuth2.AccessToken{other_params: %{"id" => id, "issued_at" => issued_at, "signature" => signature}}}) do
    calculated =
      :sha256
      |> :crypto.hmac(client_secret, id <> issued_at_raw)
      |> Base.encode64()

      calculated == signature
  end
end

I'm going to write oauth2_force (maybe without oauth2 dependency?), and refactor ex_force so that it use oauth2_force for authentication, or just use accept access token.

...or just make it part of ex_force.

chulkilee commented 6 years ago

Please take a look at #2 :)

chulkilee commented 6 years ago

master branch now has ExForce.OAuth to handle different type of grant type. Still you need to get authorization code or refresh token and pass it to ExForce.

epinault commented 6 years ago

cool! thanks!