-
Notifications
You must be signed in to change notification settings - Fork 47
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
Allow multiple instances of my app to share a Server-to-Server OAuth token #269
Comments
Thanks for the link. This is very interesting, I wasn't aware of this I think adding an |
My account is not setup with multiple token_index, therefore I can't test this new functionality. Are you in a position to test it for me? If I publish a beta package to my MyGet feed, would you be able to try it and give me some feedback? |
You are right, I also didn't find anything in documentation. I was surprised to find this behavior. I mean when new access token invalidates the previous one.
My account also doesn't support it right now. At least I have checked that zoom returns me the "Maximum group number limit exceeded" error. I found on a ticket above that this is the correct behavior... Could you also add ability to manually refresh access token? Mb by providing |
Thanks for validating.
To be honest with you, I did not intend to make it public. My goal is to shield developers from the intricacies of requesting and refreshing authentication tokens but I did make sure to include a "OnRefresh" delegate on the As always though, I'm keeping an open mind. Maybe you could expand on your scenario and explain why you need to be able to manually request/refresh authentication token? Maybe you have a scenario that I never envisioned? |
To try get access token again without instantiating a new instance of ZoomClient doesn't handle this case, so it's impossible to refetch access token if you have singleton |
Even if ZoomNet provided you a way to manually get a new token, that would not solve the problem of having to instantiate a new instance of By the way, I did some testing about this scenarios and I have a few observations: Observation number 1: it's really easy to reproduce the problem var connectionInfo1 = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId);
var connectionInfo2 = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId);
// Instantiate two clients, each with their own connectionInfo.
var client1 = new ZoomClient(connectionInfo1);
var client2 = new ZoomClient(connectionInfo2);
// Connection number 1 requests a new token prior to the API call and User.GetAsync call subsequently completes successfully
var myUser1 = await client1.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
// Connection number 2 requests a new token (thereby invalidating the token in connection number 1) and the User.GetAsync call completes successfully
var myUser2 = await client2.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
// The call to User.GetAsync fails because the token in connection number 1 has been invalidated
myUser1 = await client1.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false); Observation number 2: this problem can be mitigated by sharing the connection info rather than setting up a connection for each client. var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId);
// Instantiate two clients sharing the same connectionInfo (this is important!)
var client1 = new ZoomClient(connectionInfo);
var client2 = new ZoomClient(connectionInfo);
// Connection number 1 requests a new token and the User.GetAsync call completes successfully
var myUser1 = await client1.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
// Connection number 2 does not need to request a new token and the User.GetAsync call completes successfully
var myUser2 = await client2.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
// Both of the following calls succeed because the token in the shared connection is still valid
myUser1 = await client1.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
myUser2 = await client2.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false); Observation number 3: I can't test because my account is not setup for multiple "group numbers" but I'm fairly certain the addition of the token index solves this problem: var connectionInfo1 = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, null, 0); // <-- token index zero
var connectionInfo2 = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, null, 1); // <-- token index one
// Instantiate two clients, each with their own connectionInfo.
var client1 = new ZoomClient(connectionInfo1);
var client2 = new ZoomClient(connectionInfo2);
// Connection number 1 requests a new token for group number zero and the User.GetAsync call completes successfully
var myUser1 = await client1.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
// Connection number 2 requests a new token for group number one and the User.GetAsync call completes successfully
var myUser2 = await client2.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
// Both of the following calls succeed because the token in their respective connections are still valid
myUser1 = await client1.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false);
myUser2 = await client2.Users.GetCurrentAsync(CancellationToken.None).ConfigureAwait(false); Observation number 4: Configuring your account for multiple "group numbers" is great but not scalable. Observation number 5: This whole issue can be bypassed by creating multiple Server-to-Server OAuth apps in your Zoom account.
What this means is that you can create a large number of OAuth apps to match the number of instance of your application. Each OAuth app will have their own secret info (account id, client id and client secret) and therefore requesting/refreshing access token for one app does not interfere with the tokens for the other apps. This is great but I don't know how realistic this is. Will developers really create large number of apps (10, 20, 50, etc.) and manage three secret values for each app? |
Observation number 5: Server-to-Server connection should accept a previously issued token. This would not completely solve the problem but it's a step in the right direction. Something along these lines: // Notice the 'access token' parameter.
var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, accessToken); |
This helps to add some retry policy to refetch access token if it for some reasons isn't active
I also see that this is not really scalable solution
Yes it should get ability to reuse same access token between multiple instances, but still if you receive "Invalid access token" you should either create new instance of OAuthConnectionInfo to refetch access token either we can have ability to refetch access token in this cases (or do it manually). I just want to have not tricky way to create smth like "retry policy" for such cases |
Observation number 6: ambiguous error code when a token has been invalidated. Therefore, I can't think of a way to accurately detect when a token has been invalidated. |
Thinking out loud (feel free to challenge my ideas, let me know if you agree/disagree, etc.): For a scenario where you have multiple instances of your app, the ideal solution would be:
To accomplish this, what if I provided an interface called Here's what I think this interface could look like: public interface ITokenManagementStrategy
{
/// <summary>
/// Retrieve token information from your central repository.
/// </summary>
Task<(string AccessToken, string RefreshToken, DateTime TokenExpiration, int TokenIndex)> GetTokenInfoAsync();
/// <summary>
/// Acquire a lease prior to renewing a token to prevent other instances from renewing the token at the same time.
/// </summary>
Task AcquireLeaseAsync();
/// <summary>
/// Persist token information to your central repository.
/// </summary>
Task SaveTokenInfoAsync(string accessToken, string refreshToken, DateTime tokenExpiration, int tokenIndex);
/// <summary>
/// Release the lock that was previously acquired.
/// </summary>
Task ReleaseLeaseAsync();
} which you would use like this: public class MyTokenManagementStrategy: ITokenManagementStrategy
{
... here you would implement the interface ...
}
var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, new MyTokenManagementStrategy());
var client = new ZoomClient(connectionInfo); |
Seems good, It will be nice to see that! |
I published another beta release which contains the following enhancements:
// Old syntax
var connectionInfo = new OAuthConnection...);
// New syntax
var connectionInfo = OAuthConnection.ForServerToServer(...);
var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, 1);
var tokenIndex = 0;
var tokenRepository = new MyTokenRepository(); // <-- this is your custom implementation of the interface
var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, tokenIndex, tokenRepository);
var tokenIndices = new[] { 0, 1, 2 };
var tokenRepository = new MyTokenRepository();
var connectionInfo = OAuthConnectionInfo.ForServerToServer(clientId, clientSecret, accountId, tokenIndices, tokenRepository);
|
I have published a beta of a new nuget package that contains a class that saves token information to Azure Blog storage here. Let me know if you have a chance to try the nuget package or to use it's C# source as a model to create your own token repository class. |
I raised a separate issue for the "token_index" feature. This issue will be used to track the "token repository" feature. |
token_index
, no ability to manually refresh access token
Looks like Zoom released a solution to this scenario. This means that we will need neither the "token index" nor the "token repository". |
Please take a look at https://devforum.zoom.us/t/randomly-receiving-invalid-access-token-for-server-to-server-oauth/74432 issue at zoom.
I have multiple ZoomNet client apps which are sharing the same credentials to Zoom Server-to-Server application. And each time when one client receives new access token the previous ones are invalidated. Currently this library have no way to working with this case (no
token_index
, no ability to manually refresh access token), so I'm forced to request new access token each time before new API call.Could you advice smth or add support of
token_index
and ability to manually refresh access tokens (in order to do this at RetryCoordinator)?The text was updated successfully, but these errors were encountered: