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);
@stavro-gh
Copy link

thanx for the example but i cant get it to work with my own api.
i have a working implementation without restsharp using "native" httpclient in .net6
but if i use latest restsharp lib and your boilerplate, i have problems with auth.

i can see the injected authentication
Screenshot 2022-07-08 130912
header in the request of the client object but still getting "Request failed with status code Unauthorized"
i crosschecked token with postman where it works fine.

do you have any idea what could be wrong. assuming my code ist identical with yours and api spec as well (rest, json, oauth2) ?

@alexeyzimarev
Copy link
Author

I am not sure how would I know why your server returns an error?

@stavro-gh
Copy link

stavro-gh commented Jul 10, 2022

sure ;)

let me ask in an different way. do the values of the object in my screenshot make any sense to you for a valid token?
same token works in postman and in my own implementation using the .net6 httpclient class.
could it be, that current restsharp lib has problems with autoredirect?
i explicitly set this in my own implementation: "AllowAutoRedirect = false"
cant find an option for that in the current implementation of restsharp.

for me it looks like the client object holds the correct parameters to pass to the header in the request, but for any reason its not done at runtime.

maybe because its dropping those headers because of a redirection? (api behinde proxy)

edit: redirect seems not to be the issue here after further testing. api server ist not mine, i have no administrational access to it. its a commercial api of one of our business partners.

@alexeyzimarev
Copy link
Author

Maybe you can post the code that works using HttpClient.

@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