cancel
Showing results for 
Search instead for 
Did you mean: 

Custom Connector - Custom Auth issue

Kerplunk
Deputy Chef II
Deputy Chef II

Hello, 

I'm building my first custom connector and I'm stuck on the auth path. 
each request to the connected application requires an active Session Id & Auth Token to be included in the headers. 
to generate the Session and Auth Tokens I need post a body containing the API Key, Timestamp and a Hash 
The connection flow is this: 

  1. User enters an API Key and API Secret in the connection > fields:  block
  2. In the acquire: block, the API Key,Timestamp and Secret are joined and encrypted using the .md5_hexdigest method 
  3. the api returns an accessToken and refreshToken in the response body
  4. The accessToken and refreshToken are then included in API request as Authorization & x-fv-sessionid headers respectively: Note the refreshToken expires after 24hrs

 

I think I'm heading in the right direction, but I'm stumped on

  • how to map the acquire: block response body  hashes to the apply: block headers
  • my current error "undefined method `[]' for nil:NilClass at line: 72 called from: 84" when testing the connection. 

this is the full connection code. 

Kerplunk_0-1684504010184.png

the highlights are:

Authorization: block

authorization: {
type: "custom_auth",

#compile Auth URL
authorization_url: lambda do |connection|
["#{connection['base_uri']}","#{connection['auth_uri']}"].join()
end,
#Request Session Token

47 acquire: lambda do |connection|
48 #declare variables for API Hash and Timestamp
49 timestamp = now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z",
50 hash = ["#{connection['api_key']}","#{timestamp}","#{connection['api_secret']}"].join("/").md5_hexdigest
51
52 {
53 #initiate request for session and refresh tokens
54 auth_token: post("#{connection['base_uri']}/session").
55 headers(Accept: "application/json").
56 payload(
57 mode: "key",
58 apiKey: "#{connection['api_key']}",
59 apiHash: "#{hash}",
60 apiTimestamp: "#{timestamp}"
61 )
62 }
63 end,
64 refresh_on: [401],
this request should result in the following response from the API

{
   "accessToken": "*********",
   "refreshToken": "*********",
   "refreshTokenExpiry": 1684585412066,
   "refreshTokenTtl": "24 hours",
   "userId": "****",
   "orgId": "****"
}

I then have the apply block. 

70 apply: lambda do |connection, auth_token|
71 headers(
72 'Authorization': "Bearer #{auth_token['accessToken']}",
73 'x-fv-sessionid': "#{auth_token['refreshToken']}",
74 'Content-Type': "application/json"
75 )
76 end

And finally the test block. 

82    test: lambda do |connection|
83 get("/core/projects")
84 end,

Any assistance would be much appreciated

Ben

 

2 ACCEPTED SOLUTIONS

sergio-mier
Workato employee
Workato employee

Hey Ben,

You've made great progress on your first custom connector!
Before getting into potential fixes, I wanted to propose the following changes based on best practices:

  • "string" is not a valid control type for connection fields, so please remove lines 9 and 16. FYI, if you don't include a type attribute, the SDK will treat the connection field as "string" type by default
  • change the value for the url attribute in line 23 to ".api.com"
  • for the base_uri block, change line 29 to: "https://#{connection['domain']}.api.com"
  • remove auth_uri block (lines 31-33)
  • remove authorization_url code (lines 38-40)

Now, I believe the reason for your error is in the apply block. Because you configured a custom hash in the acquire block (lines 52-63), you would need to reference the "accessToken" and "refreshToken" fields in the apply block as connection[:auth_token]['accessToken'] and connection[:auth_token]['refreshToken'], respectively.  And FYI, "connection" should be the only argument used in the apply block in this example.

Now, adding more best practices to the above, I recommend changing the authorization block to:

authorization: {
type: "custom_auth",

acquire: lambda do |connection|
#declare variables for API Hash and Timestamp
timestamp = now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z",
hash = [connection['api_key'],timestamp,connection['api_secret']].join("/").md5_hexdigest
url = "https://#{connection['domain']}.api.com"

response = post(url).
#no need for 'Accept' header when dealing with 'application/json' content types. Workato includes this by default
payload(
mode: "key",
apiKey: connection['api_key'],
apiHash: hash,
apiTimestamp: timestamp
)

#generate simpler credentials hash
{
access_token: response['accessToken'],
refresh_token: response['refreshToken']
}
end,

refresh_on: [401],

apply: lambda do |connection|
#use 'case_sensitive_headers' method to make sure 'x-fv-sessionid' header is treated correctly
case_sensitive_headers(
'Authorization': connection['access_token'],
'x-fv-sessionid': connection['refresh_token']
#no need for 'Content-Type' header when dealing with 'application/json' content types. Workato sets this by default
)
end

}

I hope this helps!

View solution in original post

Kerplunk
Deputy Chef II
Deputy Chef II

Hi @sergio-mier , 
I've successfully connected to the API at last... !!!
to make it work I had to

  • comment out the the timestamp and apiHash variables defined in the acquire block
  • replace the references to the timestamp variable in the apiHash and acquire request payload with the timestamp formula
  • replace the apiHash value with the full apiHash formula
 payload(
     mode: "key",
     apiKey: connection['api_key'],
     apiHash: [connection['api_key'],now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z",connection['api_secret']].join("/").md5_hexdigest,
     apiTimestamp: now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z"
)

Obviously it would be cleaner to reference the variable in the above, so it would be cool if you have any insight as to why that is occurring however I'm happy to have made a successful connection. 

😃

View solution in original post

4 REPLIES 4

sergio-mier
Workato employee
Workato employee

Hey Ben,

You've made great progress on your first custom connector!
Before getting into potential fixes, I wanted to propose the following changes based on best practices:

  • "string" is not a valid control type for connection fields, so please remove lines 9 and 16. FYI, if you don't include a type attribute, the SDK will treat the connection field as "string" type by default
  • change the value for the url attribute in line 23 to ".api.com"
  • for the base_uri block, change line 29 to: "https://#{connection['domain']}.api.com"
  • remove auth_uri block (lines 31-33)
  • remove authorization_url code (lines 38-40)

Now, I believe the reason for your error is in the apply block. Because you configured a custom hash in the acquire block (lines 52-63), you would need to reference the "accessToken" and "refreshToken" fields in the apply block as connection[:auth_token]['accessToken'] and connection[:auth_token]['refreshToken'], respectively.  And FYI, "connection" should be the only argument used in the apply block in this example.

Now, adding more best practices to the above, I recommend changing the authorization block to:

authorization: {
type: "custom_auth",

acquire: lambda do |connection|
#declare variables for API Hash and Timestamp
timestamp = now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z",
hash = [connection['api_key'],timestamp,connection['api_secret']].join("/").md5_hexdigest
url = "https://#{connection['domain']}.api.com"

response = post(url).
#no need for 'Accept' header when dealing with 'application/json' content types. Workato includes this by default
payload(
mode: "key",
apiKey: connection['api_key'],
apiHash: hash,
apiTimestamp: timestamp
)

#generate simpler credentials hash
{
access_token: response['accessToken'],
refresh_token: response['refreshToken']
}
end,

refresh_on: [401],

apply: lambda do |connection|
#use 'case_sensitive_headers' method to make sure 'x-fv-sessionid' header is treated correctly
case_sensitive_headers(
'Authorization': connection['access_token'],
'x-fv-sessionid': connection['refresh_token']
#no need for 'Content-Type' header when dealing with 'application/json' content types. Workato sets this by default
)
end

}

I hope this helps!

Wow @sergio-mier, THANK YOU for these insights! Lots of helpful tips here.

@Kerplunk, please let us know if this solves your issue. I'm sure other folks here would love to hear how it goes. 😊

Cheers,
Meghan

Kerplunk
Deputy Chef II
Deputy Chef II

Hey @sergio-mier thanks so much for the quick response. the good news is that I'm no longer getting the undefined method error, but it's not quite there yet. 

a couple of points to note: 

  • the base url is [domain].api.filevineapp.com - api.com didn't work.
  • I was getting a 500 error following the initial test, so updated the refresh_on to include: [401, 500]

So after applying the above changes the test step fails as expected, however there is a new error in the acquire request. 

{
mode: "key",
apiKey: *****,
apiHash: *****,
apiTimestamp: [
"2023-05-20T08:19:05.284Z",
"a4480e1436c4740099e4388bd6c66745"
]
}

It's not clear why the apiTimestamp hash is being converted into an array or what that spurious string represents. however at the moment the error is 

  • error: Invalid "apiTimestamp"; expected an ISO-formatted date string

Also just for clarification: Am I correct in thinking that the sequence of connection functions for the initial connection is test (which fails)> refresh > acquire > test (success)??

Kerplunk
Deputy Chef II
Deputy Chef II

Hi @sergio-mier , 
I've successfully connected to the API at last... !!!
to make it work I had to

  • comment out the the timestamp and apiHash variables defined in the acquire block
  • replace the references to the timestamp variable in the apiHash and acquire request payload with the timestamp formula
  • replace the apiHash value with the full apiHash formula
 payload(
     mode: "key",
     apiKey: connection['api_key'],
     apiHash: [connection['api_key'],now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z",connection['api_secret']].join("/").md5_hexdigest,
     apiTimestamp: now.in_time_zone('Etc/UTC').strftime('%Y-%m-%dT%H:%M:%S.%L')+"Z"
)

Obviously it would be cleaner to reference the variable in the above, so it would be cool if you have any insight as to why that is occurring however I'm happy to have made a successful connection. 

😃