Background
How to implement an iPaaS-driven OAuth process in an integration. An example of the full implementation of this process for a Slack integration is included at the bottom.
Overview
OAuth is an industry standard method of validating API users. Typically, this will require some handshake requirements when setting up an integration for the first time. While the OAuth method has a well-defined standard, there is a wide range of variation in how it is implemented from system to system.
iPaaS currently supports four types of OAuth validation:
OAuth Initiated Within iPaaS
OAuth Initiated Within the External System
OAuth Initiated Within the External System, with an iPaaS driven callback
OAuth Initiated Within the External System, that requires an iPaaS login as the first step
Check the external system’s API documentation for which method will be required for your implementation.
The process will generally involve:
A user initiation, either in iPaaS or the external system
A UI-based authentication (in the non-initiating system)
A call to a method inside your integration to complete the process.
OAuth Data Flows
Below are the steps that will be followed by the various types of OAuth validation methods.
OAuth Initiated Within iPaaS
This method begins when a user clicks the Authenticate button on the Subscription Settings page after certain parameters are added to instruct iPaaS.com where to connect to:
This is the only method that is initiated within iPaaS.
After that button is clicked, the UI will make a call to the iPaaS.com API requesting to begin an OAuth validation session. That API request returns a URL to the UI based on the settings of your integration. The user is then redirected to that URL in a separate window. Typically, this URL will include an embedded callback URL that will allow the external system to notify iPaaS.com when the authorization is complete. Once the user has completed the authorization process, the callback URL is called, which calls a method in your integration.
OAuth Initiated Within the External System
This method begins when a user in the external system initiates the OAuth process. How this looks will vary from system to system but normally requires you setup an app or a token in that system.
The external system will make two calls: one to an API endpoint that will record the beginning of an OAuth validation session, followed by another call to a simplified iPaaS.com login. Once the login and system selection is complete, the OAuth session data will be passed on to a method in your integration.
OAuth Initiated Within the External System, with an iPaaS driven callback
This method is similar to the standard Externally Initiated OAuth method, with an additional step to callback to the external system once the iPaaS authentication is complete.
OAuth Initiated Within the External System, that requires an iPaaS login as the first step
This method is similar to the standard Externally Initiated OAuth method, with some additional authorization steps once the iPaaS.com login is authenticated. This allows the external system to validate that the user can connect to iPaaS.com before it grants permissions within the external system.
Setup
Ensure that any necessary information you will need is stored in iPaaS.com in either in the system's settings, or the system type version's custom fields. For example, you may need client id for the OAuthUrl that is initially called. You may also need a client secret or key for the final exchange to get a permanent token.
There are three settings you may need to configure:
OAuth Url Template
You may need to define a URL that will be called by iPaaS.com. This will be used if you are using the authorization method “OAuth Initiated Within iPaaS”, or “OAuth Initiated Within the External System, that requires an iPaaS login as the first step”. In the former case, this will be the URL that is the user is redirected to once the authorization process is initiated. In the latter, it will be where the user is redirected to once the iPaaS login is complete.
This url may include query string parameters or other variables. The OAuthUrlTemplate allows you to specify certain values as variables that will be populated at the time the OAuth process is initiated. You can specify a variable by including it in curly brackets {} in the URL. Variables can be populated from several places:
System Settings - you can specify system settings in the URL by including the name of that setting in curly brackets. In the Slack example below, you can see the Api Url is used at the begining of the template.
SystemTypeVersion CustomFields - you can specify values from the systemTypeVersion CustomFields collection by specifying the name of the custom field, prefixed by "SystemTypeVersion:" and wrapped in curly brackets. In the Slack example, you can see the ClientId custom field used in this way.
Special iPaaS-specific fields - two fields are provided by iPaaS: iPaaSRedirectUrl specifies where the external system should direct the call back (in our case, for Staging it will be a URL-encoded version of https://stagingapi.ipaas.com/HookApi/v2/Authorization/AuthorizationComplete). iPaaSState specifies an instance-specific GUID that we will use to track your authorization request.
CodeChallenge - some external systems require a code challenge parameter. This can be specified with a variable called CodeChallenge. By default, this will be generated with a length of 32 characters. If a different character length is required, it can be specified after a colon, such as CodeChallenge:12 for a 12-character code. Currently the only code challenge method supported is SHA256.
Once you have constructed your OAuthUrlTemplate, save it to the relevant SystemTypeVersion.
OAuth Identifier Field
This field indicates which query string parameter will hold the unique identifier of the transfer request. For example, the external system may send a call with the following query string, where state is the unique identifier that will be used for both the first and second calls to iPaaS.com:
code=134955000391&state=e6b8be27-36e2-4cc4-8914-71228184b65b
In this case, we would use state as the OAuth Identifier field.
The field name may change between the first call and the second call. In this case, you would set the OAuth Identifier Field to a comma-separated list of field names. If the parameter is encoded, you can prefix the field name with ENC: For example if your first phase field was named state and your second phase field was named secret and needed to be encoded, you would set the value to state,ENC:secret
OAuth Success Callback Field
This field is only used by externally drive OAuth. This field indicates which query string parameter will indicate a success callback to return control to the external system on an externally driven OAuth. E.g. your second call from an OAuth request from an external system might look like this:
In this case, success_call_back is a field that we are expected to call back to let the external system know that we have completed the OAuth process. So we would set this value to success_call_back.
Create Your ProcessAuthorization Method in TranslationUtilities
Add a procedure to your integration's TranslationUtilities class with the following method signature:
public new void ProcessAuthorization(Integration.Abstract.Connection connection, Authorization authorization)
Note that this procedure is not async, like many others in TranslationUtilities are. Add code here to process any values that were submitted to the iPaaS callback URL and convert them into a permanent token and/or save the token, as needed. Typically, this token would be saved to the PersistentData in the integration settings.
Ensure Appropriate Environmental Settings
Depending on your configuration, it may be necessary to have different settings depending on your environment. For example, your integration may use one set of custom fields in Staging to point to a staging or sandbox version of your external system, while in Production it points to a different version. Be aware of any of these settings and be sure that you each environment has the correct value once your integration is moved to Production.
Example
The following example follows the steps above using the process outlined in the Slack OAuth documentation, which can be found here: https://api.slack.com/authentication/oauth-v2
Setup
We need two things stored in the SystemTypeVersion's custom fields: the ClientId and the ClientSecret:
Determine our value for OAuthUrlTemplate.
We need several variables: Api Url from the system settings, ClientId from the SystemTypeVersion custom fields, and the two iPaaS-generated values: iPaaSRedirectUrl, and iPaaSState. We also have some hardcoded values, such as the required scopes in the scope variable. All that combined gives us a url template of:
Once we set that via the API, we can test it by calling the endpoint v2/Authorization/Initialize/13191 on the HookApi. We recieve the following response, which looks correct:
{
"state": "a007a64c-3c42-4351-b24f-cd1baa5e95ec",
"oAuthUrl": "https://slack.com/oauth/v2/authorize?scope=chat:write,users:read&client_id=bogus.5490354031155&redirect_uri=https%3a%2f%2fdevapi.ipaas.com%2fHookApi%2fv2%2fAuthorization%2fAuthorizationComplete&state=a007a64c-3c42-4351-b24f-cd1baa5e95ec"
}
Create ProcessAuthorization Method in TranslationUtilities
Per the Slack specs, when we recieve the temporary code at the callback url, we need to make one call to the endpoint https://slack.com/api/oauth.v2.access to retrieve our permanent token. Once we have the permanent token, we save the value to conn.Settings.PersistentData, which will make it available to future calls.
Here is the complete code for our method:
public new void ProcessAuthorization(Integration.Abstract.Connection connection, Authorization authorization)
{
var conn = (Connection)connection;
var bcWrapper = conn.CallWrapper;
//Pull the relevant data from the callback data. All we need now is the code
var code = authorization.CallbackData["code"];
//We need to retrieve the SystemType from the iPaaS API so that we can see the system type version and read some custom fields.
var taskSystemType = Task.Run(async () => await conn.IPaasApiCallWrapper.SystemType_GETAsync(conn.Settings.SystemTypeId));
var systemType = taskSystemType.GetAwaiter().GetResult();
//Find the specific SystemTypeVersion that matches our current system
var systemTypeVersion = systemType.Versions.Find(x => x.Id == conn.ExternalIntegrationVersionId);
//Pull the desired values out of the custom fields
var clientId = systemTypeVersion.CustomFields.GetValueOrDefault("ClientId");
var clientSecret = systemTypeVersion.CustomFields.GetValueOrDefault("ClientSecret");
//Ensure that the values we need exist
if (string.IsNullOrEmpty(clientId))
throw new Exception("Unable to complete OAuth process in ProcessAuthorization. ClientId is not specified in the CustomFields for this SystemTypeVersion");
if (string.IsNullOrEmpty(clientSecret))
throw new Exception("Unable to complete OAuth process in ProcessAuthorization. ClientSecret is not specified in the CustomFields for this SystemTypeVersion");
//Call the OAuthAccess endpoint in Slack's API
var taskOAuthAcceess = Task.Run(async () => await OAuthAccess.Get(bcWrapper, code, clientId, clientSecret));
var oauthAccess = taskOAuthAcceess.GetAwaiter().GetResult();
if(oauthAccess == null || string.IsNullOrEmpty(oauthAccess.AccessToken))
throw new Exception("Unable to complete OAuth process in ProcessAuthorization. Failed to convert temporary code into a permanent token");
var token = oauthAccess.AccessToken;
//Now save the token to our PersistentData
conn.Settings.PersistentData.SaveValue("AccessToken", token);
}
And here is the OAuthAccess.Get method references in that code:
public static async Task<OAuthAccess> Get(CallWrapper activeCallWrapper, string code, string clientId, string clientSecret)
{
string url = "https://slack.com/api/oauth.v2.access";
using (HttpClient httpClient = new HttpClient())
{
var formData = new MultipartFormDataContent();
formData.Add(new StringContent(code), "code");
formData.Add(new StringContent(clientId), "client_id");
formData.Add(new StringContent(clientSecret), "client_secret");
var response = await httpClient.PostAsync(url, formData);
string responseContent = await response.Content.ReadAsStringAsync();
// Handle the response content as per your requirements
Console.WriteLine(responseContent);
var retVal = JsonConvert.DeserializeObject<OAuthAccess>(responseContent);
return retVal;
}
}
Modify Our API Calls to Use the PersistentData
We have one last change to make to our integration. Our Slack integration is configured to allow a manually generated token to be saved in the system settings, or to use an OAuth generated token (as demonstrated above) in the PersistentData. Now we need to modify our APICall class to check both locations.
In our APICall.CreateRestRequest call, we now check the PersistentData first, and if that is empty, we use the system settings:
private RestSharp.RestRequest CreateRestRequest(string url)
{
RestSharp.RestRequest req = new RestRequest(url, Method.Get);
req.RequestFormat = DataFormat.Json;
//Check if we have an access token in the persistent data. If so, use it. If not, check the APIToken field
var accessTokenPD = this.Connection.Settings.PersistentData.GetValue("AccessToken");
if (accessTokenPD != null && accessTokenPD.Value != null)
req.AddHeader("Authorization", $"Bearer {accessTokenPD.Value}");
else
req.AddHeader("Authorization", $"Bearer {this.Connection.Settings.APIToken}");
lastRestRequestCreateDT = DateTime.Now;
return req;
}