Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for JWT OAuth flow #68

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

skostojohn
Copy link

RE Issue #66

Changes to support JWT auth flow in ex_force.
Includes:

  1. New function get_token_jwt to make the auth call and handle results
  2. New function create_jwt_payload to create the JWT assertion payload
  3. Changes to get_token to call the correct function given the grant type
  4. New test helper function assert_form_body_jwt
  5. New tests

The new function get_token_jwt could potentially be avoided, but there are enough differences from the other grant types that it seemed cleaner to create a new function rather than try to add this to the existing function.

I wasn't sure if I needed to make any documentation updates - I can do that if you can point me in the right direction.

As I mentioned earlier, I am a beginner with Elixir - I welcome any feedback and am happy to rework this to better suit your preferences or to make it more Elixir-idiomatic.

Thank you!
Scott

@sourcelevel-bot
Copy link

Hello, @skostojohn! This is your first Pull Request that will be reviewed by SourceLevel, an automatic Code Review service. It will leave comments on this diff with potential issues and style violations found in the code as you push new commits. You can also see all the issues found on this Pull Request on its review page. Please check our documentation for more information.

@skostojohn
Copy link
Author

Hi @chulkilee - please let me know if you are waiting for me on any items for this PR. I am new to this, and not sure if I need to anything else to submit it for review.
Thanks!

Copy link
Owner

@chulkilee chulkilee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi - sorry I forgot to submit the review! 🤦‍♂️

Comment on lines +236 to +237
key = %{"pem" => Keyword.fetch!(payload, :jwt_key)}
signer = Joken.Signer.create("RS256", key)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assume that jwt_key is in the PEM format. Hm.. what about taking signer?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand your suggestion about "taking signer" - can you please clarify? Are you saying that instead of the user passing in the private key to get_token via jwt_key in the payload, they should instead create a Joken.Signer struct like signer themselves (presumeably via Joken.Signer.create) , and then pass that instead?

If so, are we then tied to Joken? If you want to internally use a different approach to JWT in the future, would that impact our interface, which would be expecting a Joken.Signer struct? Also, are we then complicating the user experience by asking them to work with Joken to create the struct, when before the use of Joken was transparent to them?

In some ways here I am adopting the behavior of the restforce ruby gem for Salesforce , which I am familiar with. It also takes the private key as a parameter to support JWT authentication.

I am happy to change what I have done here, if you can just help me understand a bit more clearly what you are suggesting. Thanks!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, if I were to use this feature, I'm going to keep signer struct instead of PEM format to avoid extra decoding work on each request.

We may introduce an adapter for Joken - like it does with Tesla for http. That Joken adopter will tak any params Joken.Signer.create/2 would take.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean "extra decoding work on each request"? In my experience, you call authenticate (e.g. via get_token) once to get an access token from Salesforce, which you would then use on all subsequent calls to the API for authentication. So you only really use the pem string once, to get the access_token. The access_token you get after JWT auth is the same as the access_token you get from any other oauth flow..

Sorry that I am having trouble following your concerns here.

Comment on lines 117 to 127
def get_token(url, payload) when is_binary(url) do
url
|> build_client
|> then(
&if Keyword.fetch!(payload, :grant_type) == "jwt" do
get_token_jwt(&1, url, payload)
else
get_token(&1, payload)
end
)
end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_token(url, payload) is a thin wrapper for get_token(client, payload). Instead of adding this logic in it, let's do it inside get_token.

def get_token(client, opts) do
  grant_type = Keyword.fetch!(payload, :grant_type)
  
  client
  |> request(build_payload(opts))
  |> verify(grant_type, opts)
end

def verify_signature(response, "jwt", _), do: response
def verify_signature(response, _, opts) do
  # ...
end

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK - Let me take a crack at this. I think I understand your direction here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK - I refactored this to try and follow your direction for the get_token method. I am a bit concerned now that verify_signature is doing two jobs - verifying the signature and formatting the output into an %OAuthResponse. In the case of the jwt path, it is actually only formatting and not verifying anything at all. I wonder if we should separate the formatting into another function?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts on the changes I have made here? Hopefully you will find them to be aligned with your suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants