Skip to content

OAuth2 access token is not passed to client.client_credentials.get_token #151

@aumi9292

Description

@aumi9292

I'm creating a backend systems application on Epic's App Marketplace. I checked the box "Use Oauth 2.0," am using R4 FHIR version, and I uploaded a public key to Epic's app marketplace. I'm using 'https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4' as the BASE_URL when instantiating a FHIR::Client instance. Per Epic's documentation for a Backend Systems app, a POST request needs to be made to the token endpoint to obtain an access token. This POST request needs to look like this example shown in Epic's documentation

POST <TOKEN_URL> HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2FwcG1hcmtldC5lcGljLmNvbS9pbnRlcmNvbm5lY3QtYW1jdXJwcmQtb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.cM3o-9LzamiNP0luN1slylY3W0mo8YwYx4Bqj2uLPf_IhEUvt8Y3BiYBGlfnU_RgWaMkij9TCuhCRk7A8N6kVVxpBknd0JscKyTKeIrFqPbYk0gkWbpOjsxtkWnuKS0bdkjlRPXsaLMJ4uNfSAcViuuZ-wbLxnWX4AEZzzGXj8lt3uNKfOx7i3f-3wXZ-Mwe0qQq3pUjex7LpQA7qPBoQ2dJFXn491DBTW43QXsAebeKzWpKY94q5p2CU8qAJRBVrigbXc_C9Bsy_rknI-SGjH8X7BsIzMLLNFL5B1wIHhrkVFaZ83iLnJQQxaiFtsJUgtyaR02eSKhWdH3t40liMQ

This POST request needs to have a body that includes grant_type, client_credentials, client_assertion_type, and client_assertion. Here, client_assertion needs to be an encoded JWT token that looks like

      def payload
        {
          'iss': NON_PROD_CLIENT_ID,
          'sub': NON_PROD_CLIENT_ID,
          'aud': AUD,
          'iat': Time.zone.now.to_i,
          'exp': Time.zone.now.to_i + 4 * 60,
          'jti': SecureRandom.hex(6),
          'kid': PUBLIC_KEY_FINGERPRINT
        }
      end

      def assertion
        @token ||= JWT.encode payload, rsa_private, 'RS384', {typ: 'JWT', alg: 'RS384', kid: PUBLIC_KEY_FINGERPRINT}
      end

In FHIR::Client#set_oauth2_auth, OAuth2::Client#get_token is called with no arguments.

@client = client.client_credentials.get_token

OAuth2::Client#get_token accepts params as the first argument. In the method, it uses params to make the body of the POST request. If no params are passed, this request to get the access token will not be successful.

def get_token(params, access_token_opts = {}, access_token_class = AccessToken) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      params = Authenticator.new(id, secret, options[:auth_scheme]).apply(params)
      opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
      headers = params.delete(:headers) || {}
      if options[:token_method] == :post
        opts[:body] = params
        opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
      else
...

Can FHIR::Client#set_oauth2_auth be re-worked to accept a params argument so that I can pass the necessary parameters to the make the initial POST request to get the needed access token? Please let me know if you could use more information from me or if I'm misunderstanding the implementation.

In case it's useful, this is the terminal output when I try to run the example oauth2 code from GH

I, [2022-11-07T15:25:21.450261 #45018]  INFO -- : Initializing client with https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4
I, [2022-11-07T15:25:21.450680 #45018]  INFO -- : Configuring the client to use no authentication.
I, [2022-11-07T15:25:21.451568 #45018]  INFO -- : GETTING: https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/metadata
D, [2022-11-07T15:25:21.864158 #45018] DEBUG -- : GET - Request: {"method":"get","headers":{"User-Agent":"Ruby FHIR Client","Accept-Charset":"utf-8","Accept":"application/fhir+json"},"url":"https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/metadata","password":null,"user":null,"uri":"https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/metadata","cookie_jar":[],"payload":null,"block_response":null,"raw_response":false,"stream_log_percent":10,"ssl_opts":{"verify_ssl":1,"cert_store":{"verify_callback":null,"error":null,"error_string":null,"chain":null,"time":null}},"log":null,"max_redirects":10,"processed_headers":{"Accept":"application/fhir+json","User-Agent":"Ruby FHIR Client","Accept-Charset":"utf-8"},"processed_headers_lowercase":{"accept":"application/fhir+json","user-agent":"Ruby FHIR Client","accept-charset":"utf-8"},"args":{"method":"get","url":"https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/metadata","headers":{"User-Agent":"Ruby FHIR Client","Accept-Charset":"utf-8","Accept":"application/fhir+json"}},"before_execution_proc":null}, Response: [too large]
D, [2022-11-07T15:25:21.865255 #45018] DEBUG -- : Parsing response with {klass: FHIR::CapabilityStatement, format: application/fhir+json, code: 200}.
I, [2022-11-07T15:25:21.890849 #45018]  INFO -- : Configuring the client to use OpenID Connect OAuth2 authentication.
OAuth2::Error: invalid_client: 
{
  "error": "invalid_client",
  "error_description": null
}
from /Users/austinmiller/.rbenv/versions/3.0.4/lib/ruby/gems/3.0.0/gems/oauth2-1.4.4/lib/oauth2/client.rb:120:in `request'

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions