Skip to content

Instantly share code, notes, and snippets.

@alexeyzimarev
Last active September 14, 2023 12:40
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save alexeyzimarev/62d77bb25d7aa5bb4b9685461f8aabdd to your computer and use it in GitHub Desktop.
Save alexeyzimarev/62d77bb25d7aa5bb4b9685461f8aabdd to your computer and use it in GitHub Desktop.
using System.Text.Json.Serialization;
using RestSharp.Authenticators;
namespace RestSharp.Tests.External.Twitter;
public interface ITwitterClient {
Task<TwitterUser> GetUser(string user);
}
public class TwitterClient : ITwitterClient, IDisposable {
readonly RestClient _client;
public TwitterClient(string apiKey, string apiKeySecret) {
var options = new RestClientOptions("https://api.twitter.com/2");
_client = new RestClient(options) {
Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret)
};
}
public async Task<TwitterUser> GetUser(string user) {
var response = await _client.GetJsonAsync<TwitterSingleObject<TwitterUser>>(
"users/by/username/{user}",
new { user }
);
return response!.Data;
}
record TwitterSingleObject<T>(T Data);
public void Dispose() {
_client?.Dispose();
GC.SuppressFinalize(this);
}
}
public class TwitterAuthenticator : AuthenticatorBase {
readonly string _baseUrl;
readonly string _clientId;
readonly string _clientSecret;
public TwitterAuthenticator(string baseUrl, string clientId, string clientSecret) : base("") {
_baseUrl = baseUrl;
_clientId = clientId;
_clientSecret = clientSecret;
}
protected override async ValueTask<Parameter> GetAuthenticationParameter(string accessToken) {
var token = string.IsNullOrEmpty(Token) ? await GetToken() : Token;
return new HeaderParameter(KnownHeaders.Authorization, token);
}
async Task<string> GetToken() {
var options = new RestClientOptions(_baseUrl);
using var client = new RestClient(options) {
Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret),
};
var request = new RestRequest("oauth2/token")
.AddParameter("grant_type", "client_credentials");
var response = await client.PostAsync<TokenResponse>(request);
return $"{response!.TokenType} {response!.AccessToken}";
}
record TokenResponse {
[JsonPropertyName("token_type")]
public string TokenType { get; init; }
[JsonPropertyName("access_token")]
public string AccessToken { get; init; }
}
}
public record TwitterUser(string Id, string Name, string Username);
@tominyorks
Copy link

The documentation for this gist says

Such a client can and should be used as a singleton, as it's thread-safe and authentication-aware.

and

During the first call made by the client using the authenticator, it will find out that the Token property is empty. It will then call the GetToken function to get the token once and reuse the token going forward.

However, there's no code in this example that sets the token value back onto the base class and that means every client request will get a new token. It would probably be worthwhile updating the example to either show how to do this or state that the example doesn't do it.

@lcordy
Copy link

lcordy commented Aug 23, 2022

Massive thanks for the example @alexeyzimarev - I am new to RestSharp and your example is very helpful. However I am struggling with the dependency injection aspect in my test automation framework and @tominyorks comment hits the nail on the head for me when he states "However, there's no code in this example that sets the token value back onto the base class and that means every client request will get a new token. It would probably be worthwhile updating the example to either show how to do this or state that the example doesn't do it".

If you could go a step further to complete the picture as @tominyorks has suggested, that would be very much appreciated! Thanks

@miai-demant
Copy link

Bad example. Token is empty every request so if forces to get new one.

@miai-demant
Copy link

And what about refreshing token ?

@alexeyzimarev
Copy link
Author

@miai-demant feel free to provide a better example. I never claimed that it should be considered as production code. It is, as described, an example.

@alexeyzimarev
Copy link
Author

@lcordy

means every client request will get a new token

Adding one line after line 49 solves it, I am not sure why it's such a big issue

        Token = token;

@vanderlei-dev
Copy link

vanderlei-dev commented Jan 31, 2023

I created a updated version that handle expiration date, for anyway interested is over here:
https://gist.github.com/vanderlei-dev/665d7cd7c90aee0bf3e38e83dad5c7d2

Thanks @alexeyzimarev for the base example.

@alexeyzimarev
Copy link
Author

Cool! Thanks @vanderlei-dev

@keyse
Copy link

keyse commented May 9, 2023

v107 documentation is not correct.
_client = new RestClient(options)
{
Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret)
};

The above code will NOT compile because Authenticator is getter and can not be set.

@alexeyzimarev
Copy link
Author

I appreciate the report, but it would've been better to open an issue in the RestSharp repository rather than commenting on my old gist.

@PedroNetto404
Copy link

PedroNetto404 commented Jul 17, 2023

@keyse just inform during the creation of the RestClientOptions instance:

 async Task<TokenResponse> GetToken() 
{
        //here
       var options = new RestClientOptions(_baseUrl){ Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret) };
        //not here
        using var client = new RestClient(options) { // Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret) };

        var request = new RestRequest("oauth2/token") .AddParameter("grant_type", "client_credentials");

        return await client.PostAsync<TokenResponse>(request); 
} 

@alexeyzimarev thanks for your example, it was a great help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment