We are using Genesys SDK in a REST API, and we set a different access token on each request. We ran into an issue where other users' access tokens were being used on a different request when under a load, resulting in unauthorized exceptions.
Upon closer examination, I found that the Client.Configuration class has a static field, called "Default" that is not thread-safe. This seems to suggest that perhaps the SDK is designed so that every call uses the same auth token? I could be wrong, but just guessing.
Throughout the SDK code, I am seeing multiple places where if a value doesn't exist, it grabs it from the Default Configuration.
Here is what I did to fix the cross-authtoken issue:
Created a property that creates a new instance of configuration on each call. I am instantiating a new ApiClinet, because, if I don't, the Configuration class will instantiate one automatically and set the configuration to the default configuration.
private Configuration GenesysConfiguration
{
get
{
var configuration = new Configuration
{
AccessToken = CurrentUserAccessToken
};
configuration.ApiClient = new ApiClient(configuration);
configuration.ApiClient.setBasePath(_genesysRegion);
return configuration;
}
}
Creating a property to instantiate a new ConversationAPI for each request, and setting it to use the GenesysConfiguration property above
private ConversationsApi ConversationApi => new ConversationsApi(GenesysConfiguration);
Then I make whatever call I need to make to the API through ConversationApi
I am a bit concerned that there may be other places in the SDK source that I am not aware of where it will still try to grab the default configuration.
Do you see anything wrong with my approach? Or any recommendations?
Can you please describe how/why you are changing the access token on every API request? Typically and application will perform a single OAuth grant flow as appropriate for the application (Authorization Code, Implicit, or Client Credentials). The application will be issued an access token and that should be the access token that the application uses for all subsequent API requests.
So how are you obtaining the access token in your application that it changes with each request?
So we have a REST API that provides a wrapper around the Genesys API calls.. We then have an internally built integrated app in the Genesys Dialer. When the Agent receives/makes a call, the agent opens up our internal app in the IFrame.. The internal app calls Genesys to get the access token for the user.. Then when the user needs to do something with Genesys (e.g., set the wrapup code for the call), the access token is passed to the REST api in the header. The REST API takes the token from the header, sets the access token on the Genesys Configuration, then makes the call to Genesys using the access token of the user... Not sure about why it was done this way. Most likely it was to prevent other agents from accessing information about a call that the agent did not participate in..
My team owns the SDKs. I have asked @anon11147534 on my team to do a deeper dive on this and do some more research. Since it is after hours from him he probably won't look at it until tomorrow, but I just wanted to drop you a line and let you know we are researching this post.
Thanks,
John Carnell
Manager, Developer Engagement
In Noralogix we have used Genesys Cloud sdk for multiple regions and applications no issues cross-authtoken. You just need to initialize Configuration in proper way.
I recommend you to create Configuration in another way.
You need to create a single instance of the ApiClient initialized with region.
For every user's request create a new Configuration initialized with the user AccessToken and ApiClient from first step.
Probably you will ask why on step 1 single instance of the ApiClient. Because we need to best avoid socket exhaustion. The single instance of ApiClient will ensures that it will be shared against all instances of the Configuration.
About default configuration of the Configuration in the ApiClient, yes its little bit confusing.
But each API, for example in your case ConversationApi has constructor
public ConversationApi(PureCloudPlatform.Client.V2.Client.Configuration configuration = null) it will use configuration from the constructor and not default from the ApiClient.
Thank you... So you're saying that passing in a different Auth Token on each request, using the Auth token of the agent, is not going against the grain of the SDK? That this is OK? Or do you think we could accomplish the goal of preventing agents from accessing the conversations of calls to which the agent was not a participant via a different, Genesys preferred way? Also with all the places in the SDK where it grabs the default Configuration if a value is null, as I mentioned, I am worried that there may be another place where the Default Configuration is grabbed even with the code I pasted. Does my example code seem sound to you?
So you're saying that passing in a different Auth Token on each request, using the Auth token of the agent, is not going against the grain of the SDK?
Just follow approach above.
Or do you think we could accomplish the goal of preventing agents from accessing the conversations of calls to which the agent was not a participant via a different, Genesys preferred way?
Agent cant update conversation if he is not a participant, its by design.
Also with all the places in the SDK where it grabs the default Configuration if a value is null, as I mentioned, I am worried that there may be another place where the Default Configuration is grabbed even with the code I pasted.
Genesys use code generation for SDK, so same approach for all API.
You just need to use this constructors.
public ConversationsApi(PureCloudPlatform.Client.V2.Client.Configuration configuration = null)
{
if (configuration == null) // use the default one in Configuration
this.Configuration = PureCloudPlatform.Client.V2.Client.Configuration.Default;
else
this.Configuration = configuration;
// ensure API client has configuration ready
if (this.Configuration.ApiClient.Configuration == null)
{
this.Configuration.ApiClient.Configuration = this.Configuration;
}
}
public ApiClient(String basePath = "https://api.mypurecloud.com")
{
// Use TLS 1.2
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
if (String.IsNullOrEmpty(basePath))
throw new ArgumentException("basePath cannot be empty");
RestClient = new RestClient(basePath);
RetryConfig = DEFAULT_RETRY_CONFIG;
Configuration = Configuration.Default;
AddSerializerSettings();
}
Single instance of the ApiClient
apiClient = new ApiClient(basePath);
Multiple instances of the Configuration for users requests
var configuration = new Configuration(apiClient);
configuration.AccessToken = CurrentUserAccessToken;
var api = new ConversationsApi(configuration);
Update: I followed Taras' recommendation. I ran load testing on it, and I validated that the approach allowed for thread-safe usage of different auth tokens, without having different requests sharing the same token.
Thanks, Taras!
Then I looked to see which version of RestSharp the SDK uses, which is 106.3.1..
Then I looked at the source code here: https://github.com/restsharp/RestSharp/releases/tag/106.3.1
And sure enough, it uses an HttpWebRequest in the RestClient.cs file
The reason I wanted to double check with you is because making only one APIClient or even a pool of API clients, that will obviously require thread synchronization which will lower throughput.