diff --git a/docs/docs/advanced/authenticators.md b/docs/docs/advanced/authenticators.md
index 98c20a77e..14196d97c 100644
--- a/docs/docs/advanced/authenticators.md
+++ b/docs/docs/advanced/authenticators.md
@@ -22,7 +22,7 @@ var request = new RestRequest("/api/users/me") {
var response = await client.ExecuteAsync(request, cancellationToken);
```
-## Basic Authentication
+## Basic authentication
The `HttpBasicAuthenticator` allows you pass a username and password as a basic `Authorization` header using a base64 encoded string.
@@ -36,43 +36,89 @@ var client = new RestClient(options);
## OAuth1
For OAuth1 authentication the `OAuth1Authenticator` class provides static methods to help generate an OAuth authenticator.
+OAuth1 authenticator will add the necessary OAuth parameters to the request, including signature.
+
+The authenticator will use `HMAC SHA1` to create a signature by default.
+Each static function to create the authenticator allows you to override the default and use another method to generate the signature.
### Request token
+Getting a temporary request token is the usual first step in the 3-legged OAuth1 flow.
+Use `OAuth1Authenticator.ForRequestToken` function to get the request token authenticator.
This method requires a `consumerKey` and `consumerSecret` to authenticate.
```csharp
-var options = new RestClientOptions("https://example.com") {
+var options = new RestClientOptions("https://api.twitter.com") {
Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret)
};
var client = new RestClient(options);
+var request = new RestRequest("oauth/request_token");
```
+The response should contain the token and the token secret, which can then be used to complete the authorization process.
+If you need to provide the callback URL, assign the `CallbackUrl` property of the authenticator to the callback destination.
+
### Access token
-This method retrieves an access token when provided `consumerKey`, `consumerSecret`, `oauthToken`, and `oauthTokenSecret`.
+Getting an access token is the usual third step in the 3-legged OAuth1 flow.
+This method retrieves an access token when provided `consumerKey`, `consumerSecret`, `oauthToken`, and `oauthTokenSecret`.
+If you don't have a token for this call, you need to make a call to get the request token as described above.
```csharp
var authenticator = OAuth1Authenticator.ForAccessToken(
consumerKey, consumerSecret, oauthToken, oauthTokenSecret
);
-var options = new RestClientOptions("https://example.com") {
+var options = new RestClientOptions("https://api.twitter.com") {
Authenticator = authenticator
};
var client = new RestClient(options);
+var request = new RestRequest("oauth/access_token");
+```
+
+If the second step in 3-leg OAuth1 flow returned a verifier value, you can use another overload of `ForAccessToken`:
+
+```csharp
+var authenticator = OAuth1Authenticator.ForAccessToken(
+ consumerKey, consumerSecret, oauthToken, oauthTokenSecret, verifier
+);
```
-This method also includes an optional parameter to specify the `OAuthSignatureMethod`.
+The response should contain the access token that can be used to make calls to protected resources.
+
+For refreshing access tokens, use one of the two overloads of `ForAccessToken` that accept `sessionHandle`.
+
+### Protected resource
+
+When the access token is available, use `ForProtectedResource` function to get the authenticator for accessing protected resources.
+
```csharp
var authenticator = OAuth1Authenticator.ForAccessToken(
- consumerKey, consumerSecret, oauthToken, oauthTokenSecret,
- OAuthSignatureMethod.PlainText
+ consumerKey, consumerSecret, accessToken, accessTokenSecret
+);
+var options = new RestClientOptions("https://api.twitter.com/1.1") {
+ Authenticator = authenticator
+};
+var client = new RestClient(options);
+var request = new RestRequest("statuses/update.json", Method.Post)
+ .AddParameter("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
+ .AddParameter("include_entities", "true");
+```
+
+### xAuth
+
+xAuth is a simplified version of OAuth1. It allows sending the username and password as `x_auth_username` and `x_auth_password` request parameters and directly get the access token. xAuth is not widely supported, but RestSharp still allows using it.
+
+Create an xAuth authenticator using `OAuth1Authenticator.ForClientAuthentication` function:
+
+```csharp
+var authenticator = OAuth1Authenticator.ForClientAuthentication(
+ consumerKey, consumerSecret, username, password
);
```
### 0-legged OAuth
-The same access token authenticator can be used in 0-legged OAuth scenarios by providing `null` for the `consumerSecret`.
+The access token authenticator can be used in 0-legged OAuth scenarios by providing `null` for the `consumerSecret`.
```csharp
var authenticator = OAuth1Authenticator.ForAccessToken(
@@ -120,7 +166,7 @@ For each request, it will add an `Authorization` header with the value `Bearer <
As you might need to refresh the token from, you can use the `SetBearerToken` method to update the token.
-## Custom Authenticator
+## Custom authenticator
You can write your own implementation by implementing `IAuthenticator` and
registering it with your RestClient:
diff --git a/src/RestSharp/Authenticators/HttpBasicAuth.cs b/src/RestSharp/Authenticators/HttpBasicAuthenticator.cs
similarity index 87%
rename from src/RestSharp/Authenticators/HttpBasicAuth.cs
rename to src/RestSharp/Authenticators/HttpBasicAuthenticator.cs
index 89691c690..07d775664 100644
--- a/src/RestSharp/Authenticators/HttpBasicAuth.cs
+++ b/src/RestSharp/Authenticators/HttpBasicAuthenticator.cs
@@ -24,9 +24,9 @@ namespace RestSharp.Authenticators;
/// UTF-8 is used by default but some servers might expect ISO-8859-1 encoding.
///
[PublicAPI]
-public class HttpBasicAuth(string username, string password, Encoding encoding)
+public class HttpBasicAuthenticator(string username, string password, Encoding encoding)
: AuthenticatorBase(GetHeader(username, password, encoding)) {
- public HttpBasicAuth(string username, string password) : this(username, password, Encoding.UTF8) { }
+ public HttpBasicAuthenticator(string username, string password) : this(username, password, Encoding.UTF8) { }
static string GetHeader(string username, string password, Encoding encoding)
=> Convert.ToBase64String(encoding.GetBytes($"{username}:{password}"));
diff --git a/src/RestSharp/Authenticators/OAuth/OAuth1Auth.cs b/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
similarity index 73%
rename from src/RestSharp/Authenticators/OAuth/OAuth1Auth.cs
rename to src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
index 81ee50389..4960bb12f 100644
--- a/src/RestSharp/Authenticators/OAuth/OAuth1Auth.cs
+++ b/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
@@ -22,7 +22,7 @@
namespace RestSharp.Authenticators;
/// RFC: The OAuth 1.0 Protocol
-public class OAuth1Auth : IAuthenticator {
+public class OAuth1Authenticator : IAuthenticator {
public virtual string? Realm { get; set; }
public virtual OAuthParameterHandling ParameterHandling { get; set; }
public virtual OAuthSignatureMethod SignatureMethod { get; set; }
@@ -56,12 +56,19 @@ public ValueTask Authenticate(IRestClient client, RestRequest request) {
ClientPassword = ClientPassword
};
- AddOAuthData(client, request, workflow);
+ AddOAuthData(client, request, workflow, Type, Realm);
return default;
}
+ ///
+ /// Creates an authenticator to retrieve a request token.
+ ///
+ /// Consumer or API key
+ /// Consumer or API secret
+ /// Signature method, default is HMAC SHA1
+ /// Authenticator instance
[PublicAPI]
- public static OAuth1Auth ForRequestToken(
+ public static OAuth1Authenticator ForRequestToken(
string consumerKey,
string? consumerSecret,
OAuthSignatureMethod signatureMethod = OAuthSignatureMethod.HmacSha1
@@ -75,8 +82,15 @@ public static OAuth1Auth ForRequestToken(
Type = OAuthType.RequestToken
};
+ ///
+ /// Creates an authenticator to retrieve a request token with custom callback.
+ ///
+ /// Consumer or API key
+ /// Consumer or API secret
+ /// URL to where the user will be redirected to after authhentication
+ /// Authenticator instance
[PublicAPI]
- public static OAuth1Auth ForRequestToken(string consumerKey, string? consumerSecret, string callbackUrl) {
+ public static OAuth1Authenticator ForRequestToken(string consumerKey, string? consumerSecret, string callbackUrl) {
var authenticator = ForRequestToken(consumerKey, consumerSecret);
authenticator.CallbackUrl = callbackUrl;
@@ -84,8 +98,17 @@ public static OAuth1Auth ForRequestToken(string consumerKey, string? consumerSec
return authenticator;
}
+ ///
+ /// Creates an authenticator to retrieve an access token using the request token.
+ ///
+ /// Consumer or API key
+ /// Consumer or API secret
+ /// Request token
+ /// Request token secret
+ /// Signature method, default is HMAC SHA1
+ /// Authenticator instance
[PublicAPI]
- public static OAuth1Auth ForAccessToken(
+ public static OAuth1Authenticator ForAccessToken(
string consumerKey,
string? consumerSecret,
string token,
@@ -103,8 +126,17 @@ public static OAuth1Auth ForAccessToken(
Type = OAuthType.AccessToken
};
+ ///
+ /// Creates an authenticator to retrieve an access token using the request token and a verifier.
+ ///
+ /// Consumer or API key
+ /// Consumer or API secret
+ /// Request token
+ /// Request token secret
+ /// Verifier received from the API server
+ /// Authenticator instance
[PublicAPI]
- public static OAuth1Auth ForAccessToken(
+ public static OAuth1Authenticator ForAccessToken(
string consumerKey,
string? consumerSecret,
string token,
@@ -119,7 +151,7 @@ string verifier
}
[PublicAPI]
- public static OAuth1Auth ForAccessTokenRefresh(
+ public static OAuth1Authenticator ForAccessTokenRefresh(
string consumerKey,
string? consumerSecret,
string token,
@@ -134,7 +166,7 @@ string sessionHandle
}
[PublicAPI]
- public static OAuth1Auth ForAccessTokenRefresh(
+ public static OAuth1Authenticator ForAccessTokenRefresh(
string consumerKey,
string? consumerSecret,
string token,
@@ -151,7 +183,7 @@ string sessionHandle
}
[PublicAPI]
- public static OAuth1Auth ForClientAuthentication(
+ public static OAuth1Authenticator ForClientAuthentication(
string consumerKey,
string? consumerSecret,
string username,
@@ -169,8 +201,17 @@ public static OAuth1Auth ForClientAuthentication(
Type = OAuthType.ClientAuthentication
};
+ ///
+ /// Creates an authenticator to make calls to protected resources using the access token.
+ ///
+ /// Consumer or API key
+ /// Consumer or API secret
+ /// Access token
+ /// Access token secret
+ /// Signature method, default is HMAC SHA1
+ /// Authenticator instance
[PublicAPI]
- public static OAuth1Auth ForProtectedResource(
+ public static OAuth1Authenticator ForProtectedResource(
string consumerKey,
string? consumerSecret,
string accessToken,
@@ -188,7 +229,13 @@ public static OAuth1Auth ForProtectedResource(
TokenSecret = accessTokenSecret
};
- void AddOAuthData(IRestClient client, RestRequest request, OAuthWorkflow workflow) {
+ internal static void AddOAuthData(
+ IRestClient client,
+ RestRequest request,
+ OAuthWorkflow workflow,
+ OAuthType type,
+ string? realm
+ ) {
var requestUrl = client.BuildUriWithoutQueryParameters(request).AbsoluteUri;
if (requestUrl.Contains('?'))
@@ -204,13 +251,6 @@ void AddOAuthData(IRestClient client, RestRequest request, OAuthWorkflow workflo
var method = request.Method.ToString().ToUpperInvariant();
var parameters = new WebPairCollection();
- // include all GET and POST parameters before generating the signature
- // according to the RFC 5849 - The OAuth 1.0 Protocol
- // http://tools.ietf.org/html/rfc5849#section-3.4.1
- // if this change causes trouble we need to introduce a flag indicating the specific OAuth implementation level,
- // or implement a separate class for each OAuth version
- static bool BaseQuery(Parameter x) => x.Type is ParameterType.GetOrPost or ParameterType.QueryString;
-
var query =
request.AlwaysMultipartFormData || request.Files.Count > 0
? x => BaseQuery(x) && x.Name != null && x.Name.StartsWith("oauth_")
@@ -219,22 +259,19 @@ void AddOAuthData(IRestClient client, RestRequest request, OAuthWorkflow workflo
parameters.AddRange(client.DefaultParameters.Where(query).ToWebParameters());
parameters.AddRange(request.Parameters.Where(query).ToWebParameters());
- if (Type == OAuthType.RequestToken)
- workflow.RequestTokenUrl = url;
- else
- workflow.AccessTokenUrl = url;
+ workflow.RequestUrl = url;
- var oauth = Type switch {
- OAuthType.RequestToken => workflow.BuildRequestTokenInfo(method, parameters),
+ var oauth = type switch {
+ OAuthType.RequestToken => workflow.BuildRequestTokenSignature(method, parameters),
OAuthType.AccessToken => workflow.BuildAccessTokenSignature(method, parameters),
OAuthType.ClientAuthentication => workflow.BuildClientAuthAccessTokenSignature(method, parameters),
- OAuthType.ProtectedResource => workflow.BuildProtectedResourceSignature(method, parameters, url),
+ OAuthType.ProtectedResource => workflow.BuildProtectedResourceSignature(method, parameters),
_ => throw new ArgumentOutOfRangeException(nameof(Type))
};
oauth.Parameters.Add("oauth_signature", oauth.Signature);
- var oauthParameters = ParameterHandling switch {
+ var oauthParameters = workflow.ParameterHandling switch {
OAuthParameterHandling.HttpAuthorizationHeader => CreateHeaderParameters(),
OAuthParameterHandling.UrlOrPostParameters => CreateUrlParameters(),
_ => throw new ArgumentOutOfRangeException(nameof(ParameterHandling))
@@ -243,7 +280,14 @@ void AddOAuthData(IRestClient client, RestRequest request, OAuthWorkflow workflo
request.AddOrUpdateParameters(oauthParameters);
return;
- IEnumerable CreateHeaderParameters() => new[] { new HeaderParameter(KnownHeaders.Authorization, GetAuthorizationHeader()) };
+ // include all GET and POST parameters before generating the signature
+ // according to the RFC 5849 - The OAuth 1.0 Protocol
+ // http://tools.ietf.org/html/rfc5849#section-3.4.1
+ // if this change causes trouble we need to introduce a flag indicating the specific OAuth implementation level,
+ // or implement a separate class for each OAuth version
+ static bool BaseQuery(Parameter x) => x.Type is ParameterType.GetOrPost or ParameterType.QueryString;
+
+ IEnumerable CreateHeaderParameters() => [new HeaderParameter(KnownHeaders.Authorization, GetAuthorizationHeader())];
IEnumerable CreateUrlParameters() => oauth.Parameters.Select(p => new GetOrPostParameter(p.Name, HttpUtility.UrlDecode(p.Value)));
@@ -254,7 +298,7 @@ string GetAuthorizationHeader() {
.Select(x => x.GetQueryParameter(true))
.ToList();
- if (!Realm.IsEmpty()) oathParameters.Insert(0, $"realm=\"{OAuthTools.UrlEncodeRelaxed(Realm)}\"");
+ if (!realm.IsEmpty()) oathParameters.Insert(0, $"realm=\"{OAuthTools.UrlEncodeRelaxed(realm)}\"");
return $"OAuth {string.Join(",", oathParameters)}";
}
diff --git a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs
index 1f54edafa..696440bd4 100644
--- a/src/RestSharp/Authenticators/OAuth/OAuthTools.cs
+++ b/src/RestSharp/Authenticators/OAuth/OAuthTools.cs
@@ -132,7 +132,7 @@ public static string GetNonce() {
///
///
///
- static string NormalizeRequestParameters(WebPairCollection parameters) => string.Join("&", SortParametersExcludingSignature(parameters));
+ internal static string NormalizeRequestParameters(WebPairCollection parameters) => string.Join("&", SortParametersExcludingSignature(parameters));
///
/// Sorts a by name, and then value if equal.
@@ -193,24 +193,7 @@ public static string GetSignature(
string signatureBase,
string? consumerSecret
)
- => GetSignature(signatureMethod, OAuthSignatureTreatment.Escaped, signatureBase, consumerSecret, null);
-
- ///
- /// Creates a signature value given a signature base and the consumer secret.
- /// This method is used when the token secret is currently unknown.
- ///
- /// The hashing method
- /// The treatment to use on a signature value
- /// The signature base
- /// The consumer key
- ///
- public static string GetSignature(
- OAuthSignatureMethod signatureMethod,
- OAuthSignatureTreatment signatureTreatment,
- string signatureBase,
- string? consumerSecret
- )
- => GetSignature(signatureMethod, signatureTreatment, signatureBase, consumerSecret, null);
+ => GetSignature(signatureMethod, OAuthSignatureTreatment.Escaped, signatureBase, consumerSecret);
///
/// Creates a signature value given a signature base and the consumer secret and a known token secret.
@@ -226,7 +209,7 @@ public static string GetSignature(
OAuthSignatureTreatment signatureTreatment,
string signatureBase,
string? consumerSecret,
- string? tokenSecret
+ string? tokenSecret = null
) {
if (tokenSecret.IsEmpty()) tokenSecret = string.Empty;
if (consumerSecret.IsEmpty()) consumerSecret = string.Empty;
@@ -250,7 +233,8 @@ public static string GetSignature(
return result;
string GetRsaSignature() {
- using var provider = new RSACryptoServiceProvider { PersistKeyInCsp = false };
+ using var provider = new RSACryptoServiceProvider();
+ provider.PersistKeyInCsp = false;
provider.FromXmlString(unencodedConsumerSecret);
diff --git a/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs b/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs
index 2039fb069..052325324 100644
--- a/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs
+++ b/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs
@@ -34,8 +34,10 @@ sealed class OAuthWorkflow {
public OAuthParameterHandling ParameterHandling { get; set; }
public string? ClientUsername { get; init; }
public string? ClientPassword { get; init; }
- public string? RequestTokenUrl { get; set; }
- public string? AccessTokenUrl { get; set; }
+ public string? RequestUrl { get; set; }
+
+ internal Func GetTimestamp { get; init; } = OAuthTools.GetTimestamp;
+ internal Func GetNonce { get; init; } = OAuthTools.GetNonce;
///
/// Generates an OAuth signature to pass to an
@@ -45,19 +47,20 @@ sealed class OAuthWorkflow {
/// The HTTP method for the intended request
/// Any existing, non-OAuth query parameters desired in the request
///
- public OAuthParameters BuildRequestTokenInfo(string method, WebPairCollection parameters) {
- ValidateTokenRequestState();
+ public OAuthParameters BuildRequestTokenSignature(string method, WebPairCollection parameters) {
+ Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
var allParameters = new WebPairCollection();
allParameters.AddRange(parameters);
- var timestamp = OAuthTools.GetTimestamp();
- var nonce = OAuthTools.GetNonce();
+ var uri = new Uri(Ensure.NotEmptyString(RequestUrl, nameof(RequestUrl)));
+ var timestamp = GetTimestamp();
+ var nonce = GetNonce();
var authParameters = GenerateAuthParameters(timestamp, nonce);
allParameters.AddRange(authParameters);
- var signatureBase = OAuthTools.ConcatenateRequestElements(method, Ensure.NotNull(RequestTokenUrl, nameof(RequestTokenUrl)), allParameters);
+ var signatureBase = OAuthTools.ConcatenateRequestElements(method, uri.ToString(), allParameters);
return new OAuthParameters {
Signature = OAuthTools.GetSignature(SignatureMethod, SignatureTreatment, signatureBase, ConsumerSecret),
@@ -73,14 +76,15 @@ public OAuthParameters BuildRequestTokenInfo(string method, WebPairCollection pa
/// The HTTP method for the intended request
/// Any existing, non-OAuth query parameters desired in the request
public OAuthParameters BuildAccessTokenSignature(string method, WebPairCollection parameters) {
- ValidateAccessRequestState();
+ Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
+ Ensure.NotEmpty(Token, nameof(Token));
var allParameters = new WebPairCollection();
allParameters.AddRange(parameters);
- var uri = new Uri(Ensure.NotEmptyString(AccessTokenUrl, nameof(AccessTokenUrl)));
- var timestamp = OAuthTools.GetTimestamp();
- var nonce = OAuthTools.GetNonce();
+ var uri = new Uri(Ensure.NotEmptyString(RequestUrl, nameof(RequestUrl)));
+ var timestamp = GetTimestamp();
+ var nonce = GetNonce();
var authParameters = GenerateAuthParameters(timestamp, nonce);
allParameters.AddRange(authParameters);
@@ -101,14 +105,15 @@ public OAuthParameters BuildAccessTokenSignature(string method, WebPairCollectio
/// The HTTP method for the intended request
/// Any existing, non-OAuth query parameters desired in the request
public OAuthParameters BuildClientAuthAccessTokenSignature(string method, WebPairCollection parameters) {
- ValidateClientAuthAccessRequestState();
+ Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
+ Ensure.NotEmpty(ClientUsername, nameof(ClientUsername));
var allParameters = new WebPairCollection();
allParameters.AddRange(parameters);
- var uri = new Uri(Ensure.NotNull(AccessTokenUrl, nameof(AccessTokenUrl)));
- var timestamp = OAuthTools.GetTimestamp();
- var nonce = OAuthTools.GetNonce();
+ var uri = new Uri(Ensure.NotEmptyString(RequestUrl, nameof(RequestUrl)));
+ var timestamp = GetTimestamp();
+ var nonce = GetNonce();
var authParameters = GenerateXAuthParameters(timestamp, nonce);
allParameters.AddRange(authParameters);
@@ -121,25 +126,25 @@ public OAuthParameters BuildClientAuthAccessTokenSignature(string method, WebPai
};
}
- public OAuthParameters BuildProtectedResourceSignature(string method, WebPairCollection parameters, string url) {
- ValidateProtectedResourceState();
+ public OAuthParameters BuildProtectedResourceSignature(string method, WebPairCollection parameters) {
+ Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
var allParameters = new WebPairCollection();
allParameters.AddRange(parameters);
// Include url parameters in query pool
- var uri = new Uri(url);
+ var uri = new Uri(Ensure.NotEmptyString(RequestUrl, nameof(RequestUrl)));
var urlParameters = HttpUtility.ParseQueryString(uri.Query);
allParameters.AddRange(urlParameters.AllKeys.Select(x => new WebPair(x!, urlParameters[x]!)));
- var timestamp = OAuthTools.GetTimestamp();
- var nonce = OAuthTools.GetNonce();
+ var timestamp = GetTimestamp();
+ var nonce = GetNonce();
var authParameters = GenerateAuthParameters(timestamp, nonce);
allParameters.AddRange(authParameters);
- var signatureBase = OAuthTools.ConcatenateRequestElements(method, url, allParameters);
+ var signatureBase = OAuthTools.ConcatenateRequestElements(method, uri.ToString(), allParameters);
return new OAuthParameters {
Signature = OAuthTools.GetSignature(SignatureMethod, SignatureTreatment, signatureBase, ConsumerSecret, TokenSecret),
@@ -147,25 +152,6 @@ public OAuthParameters BuildProtectedResourceSignature(string method, WebPairCol
};
}
- void ValidateTokenRequestState() {
- Ensure.NotEmpty(RequestTokenUrl, nameof(RequestTokenUrl));
- Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
- }
-
- void ValidateAccessRequestState() {
- Ensure.NotEmpty(AccessTokenUrl, nameof(AccessTokenUrl));
- Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
- Ensure.NotEmpty(Token, nameof(Token));
- }
-
- void ValidateClientAuthAccessRequestState() {
- Ensure.NotEmpty(AccessTokenUrl, nameof(AccessTokenUrl));
- Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
- Ensure.NotEmpty(ClientUsername, nameof(ClientUsername));
- }
-
- void ValidateProtectedResourceState() => Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey));
-
WebPairCollection GenerateAuthParameters(string timestamp, string nonce)
=> new WebPairCollection {
new("oauth_consumer_key", Ensure.NotNull(ConsumerKey, nameof(ConsumerKey)), true),
@@ -173,13 +159,14 @@ WebPairCollection GenerateAuthParameters(string timestamp, string nonce)
new("oauth_signature_method", SignatureMethod.ToRequestValue()),
new("oauth_timestamp", timestamp),
new("oauth_version", Version ?? "1.0")
- }.AddNotEmpty("oauth_token", Token!, true)
- .AddNotEmpty("oauth_callback", CallbackUrl!, true)
- .AddNotEmpty("oauth_verifier", Verifier!)
- .AddNotEmpty("oauth_session_handle", SessionHandle!);
+ }
+ .AddNotEmpty("oauth_token", Token, true)
+ .AddNotEmpty("oauth_callback", CallbackUrl, true)
+ .AddNotEmpty("oauth_verifier", Verifier)
+ .AddNotEmpty("oauth_session_handle", SessionHandle);
WebPairCollection GenerateXAuthParameters(string timestamp, string nonce)
- => new() {
+ => [
new("x_auth_username", Ensure.NotNull(ClientUsername, nameof(ClientUsername))),
new("x_auth_password", Ensure.NotNull(ClientPassword, nameof(ClientPassword))),
new("x_auth_mode", "client_auth"),
@@ -188,7 +175,7 @@ WebPairCollection GenerateXAuthParameters(string timestamp, string nonce)
new("oauth_timestamp", timestamp),
new("oauth_nonce", nonce),
new("oauth_version", Version ?? "1.0")
- };
+ ];
internal class OAuthParameters {
public WebPairCollection Parameters { get; init; } = null!;
diff --git a/src/RestSharp/Authenticators/OAuth2/OAuth2AuthorizationRequestHeaderAuthenticator.cs b/src/RestSharp/Authenticators/OAuth2/OAuth2AuthorizationRequestHeaderAuthenticator.cs
index 59f9a71a0..55a6bf5d5 100644
--- a/src/RestSharp/Authenticators/OAuth2/OAuth2AuthorizationRequestHeaderAuthenticator.cs
+++ b/src/RestSharp/Authenticators/OAuth2/OAuth2AuthorizationRequestHeaderAuthenticator.cs
@@ -24,18 +24,12 @@ namespace RestSharp.Authenticators.OAuth2;
public class OAuth2AuthorizationRequestHeaderAuthenticator : AuthenticatorBase {
readonly string _tokenType;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The access token.
- public OAuth2AuthorizationRequestHeaderAuthenticator(string accessToken) : this(accessToken, "OAuth") { }
-
///
/// Initializes a new instance of the class.
///
/// The access token.
/// The token type.
- public OAuth2AuthorizationRequestHeaderAuthenticator(string accessToken, string tokenType) : base(accessToken) => _tokenType = tokenType;
+ public OAuth2AuthorizationRequestHeaderAuthenticator(string accessToken, string tokenType = "OAuth") : base(accessToken) => _tokenType = tokenType;
protected override ValueTask GetAuthenticationParameter(string accessToken)
=> new(new HeaderParameter(KnownHeaders.Authorization, $"{_tokenType} {accessToken}"));
diff --git a/src/RestSharp/Extensions/CookieContainerExtensions.cs b/src/RestSharp/Extensions/CookieContainerExtensions.cs
index 519a497d3..8df9142d7 100644
--- a/src/RestSharp/Extensions/CookieContainerExtensions.cs
+++ b/src/RestSharp/Extensions/CookieContainerExtensions.cs
@@ -13,8 +13,6 @@
// limitations under the License.
//
-using System.Net;
-
namespace RestSharp.Extensions;
static class CookieContainerExtensions {
diff --git a/src/RestSharp/KnownHeaders.cs b/src/RestSharp/KnownHeaders.cs
index 2cd925710..3871c4d59 100644
--- a/src/RestSharp/KnownHeaders.cs
+++ b/src/RestSharp/KnownHeaders.cs
@@ -47,4 +47,4 @@ public static class KnownHeaders {
static readonly HashSet ContentHeadersHash = new(ContentHeaders, StringComparer.InvariantCultureIgnoreCase);
internal static bool IsContentHeader(string key) => ContentHeadersHash.Contains(key);
-}
+}
\ No newline at end of file
diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs
index ba28ff3d0..55e02c70c 100644
--- a/src/RestSharp/RestClient.Async.cs
+++ b/src/RestSharp/RestClient.Async.cs
@@ -12,15 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using System.Net;
using RestSharp.Extensions;
-using RestSharp.Interceptors;
namespace RestSharp;
public partial class RestClient {
// Default HttpClient timeout
- public TimeSpan DefaultTimeout = TimeSpan.FromSeconds(100);
+ readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(100);
+
///
public async Task ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default) {
using var internalResponse = await ExecuteRequestAsync(request, cancellationToken).ConfigureAwait(false);
@@ -115,7 +114,7 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo
message.Headers.Host = Options.BaseHost;
message.Headers.CacheControl = request.CachePolicy ?? Options.CachePolicy;
- using var timeoutCts = new CancellationTokenSource(request.Timeout ?? Options.Timeout ?? DefaultTimeout);
+ using var timeoutCts = new CancellationTokenSource(request.Timeout ?? Options.Timeout ?? _defaultTimeout);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken);
var ct = cts.Token;
diff --git a/test/RestSharp.InteractiveTests/AuthenticationTests.cs b/test/RestSharp.InteractiveTests/AuthenticationTests.cs
index 8c0aea8c0..4930d6ca0 100644
--- a/test/RestSharp.InteractiveTests/AuthenticationTests.cs
+++ b/test/RestSharp.InteractiveTests/AuthenticationTests.cs
@@ -18,7 +18,7 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
using var client = new RestClient(
baseUrl,
options =>
- options.Authenticator = OAuth1Auth.ForRequestToken(
+ options.Authenticator = OAuth1Authenticator.ForRequestToken(
twitterKeys.ConsumerKey!,
twitterKeys.ConsumerSecret,
"https://restsharp.dev"
@@ -47,7 +47,7 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
var verifier = Console.ReadLine();
request = new RestRequest("oauth/access_token") {
- Authenticator = OAuth1Auth.ForAccessToken(
+ Authenticator = OAuth1Authenticator.ForAccessToken(
twitterKeys.ConsumerKey!,
twitterKeys.ConsumerSecret,
oauthToken!,
@@ -69,7 +69,7 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
Assert.NotNull(oauthTokenSecret);
request = new RestRequest("1.1/account/verify_credentials.json") {
- Authenticator = OAuth1Auth.ForProtectedResource(
+ Authenticator = OAuth1Authenticator.ForProtectedResource(
twitterKeys.ConsumerKey!,
twitterKeys.ConsumerSecret,
oauthToken!,
diff --git a/test/RestSharp.InteractiveTests/TwitterClient.cs b/test/RestSharp.InteractiveTests/TwitterClient.cs
index 6324acf26..24b95e8c7 100644
--- a/test/RestSharp.InteractiveTests/TwitterClient.cs
+++ b/test/RestSharp.InteractiveTests/TwitterClient.cs
@@ -93,7 +93,7 @@ protected override async ValueTask GetAuthenticationParameter(string
async Task GetToken() {
var options = new RestClientOptions(_baseUrl) {
- Authenticator = new HttpBasicAuth(_clientId, _clientSecret)
+ Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret)
};
using var client = new RestClient(options);
diff --git a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs
index 249818c00..6a8a43df2 100644
--- a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs
+++ b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs
@@ -12,7 +12,7 @@ public async Task Can_Authenticate_With_Basic_Http_Auth() {
using var client = new RestClient(
server.Url!,
- o => o.Authenticator = new HttpBasicAuth(userName, password)
+ o => o.Authenticator = new HttpBasicAuthenticator(userName, password)
);
var request = new RestRequest("headers");
var response = await client.GetAsync(request);
diff --git a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs
index 981b552ee..fd2f6905f 100644
--- a/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs
+++ b/test/RestSharp.Tests.Integrated/NonProtocolExceptionHandlingTests.cs
@@ -45,7 +45,7 @@ public async Task Handles_HttpClient_Timeout_Error() {
public async Task Handles_Server_Timeout_Error() {
using var client = new RestClient(_server.Url!);
- var request = new RestRequest("404") { Timeout = TimeSpan.FromMilliseconds(500) };
+ var request = new RestRequest("timeout") { Timeout = TimeSpan.FromMilliseconds(500) };
var response = await client.ExecuteAsync(request);
response.ErrorException.Should().BeOfType();
diff --git a/test/RestSharp.Tests/Auth/HttpBasicAuthTests.cs b/test/RestSharp.Tests/Auth/HttpBasicAuthTests.cs
index fd2d16746..a8f3ea3ef 100644
--- a/test/RestSharp.Tests/Auth/HttpBasicAuthTests.cs
+++ b/test/RestSharp.Tests/Auth/HttpBasicAuthTests.cs
@@ -7,7 +7,7 @@ public class HttpBasicAuthTests {
const string Username = "username";
const string Password = "password";
- readonly HttpBasicAuth _auth = new(Username, Password);
+ readonly HttpBasicAuthenticator _auth = new(Username, Password);
[Fact]
public async Task Authenticate_ShouldAddAuthorizationParameter_IfPreviouslyUnassigned() {
diff --git a/test/RestSharp.Tests/Auth/OAuth1AuthTests.cs b/test/RestSharp.Tests/Auth/OAuth1AuthTests.cs
index 36b1b0539..cbbcb0401 100644
--- a/test/RestSharp.Tests/Auth/OAuth1AuthTests.cs
+++ b/test/RestSharp.Tests/Auth/OAuth1AuthTests.cs
@@ -4,7 +4,7 @@
namespace RestSharp.Tests.Auth;
public class OAuth1AuthTests {
- readonly OAuth1Auth _auth = new() {
+ readonly OAuth1Authenticator _auth = new() {
CallbackUrl = "CallbackUrl",
ClientPassword = "ClientPassword",
Type = OAuthType.ClientAuthentication,
diff --git a/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs b/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs
new file mode 100644
index 000000000..b489b3dc6
--- /dev/null
+++ b/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs
@@ -0,0 +1,52 @@
+using RestSharp.Authenticators;
+using RestSharp.Authenticators.OAuth;
+
+namespace RestSharp.Tests.Auth;
+
+public class OAuth1SignatureTests {
+ readonly OAuthWorkflow _workflow = new() {
+ ParameterHandling = OAuthParameterHandling.UrlOrPostParameters,
+ Token = "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
+ TokenSecret = "LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE",
+ ConsumerKey = "xvz1evFS4wEEPTGEFPHBog",
+ ConsumerSecret = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw",
+ SignatureMethod = OAuthSignatureMethod.HmacSha1,
+ Version = "1.0",
+ GetNonce = () => "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
+ GetTimestamp = () => "1318622958",
+ };
+
+ readonly RestClient _client = new("https://api.twitter.com/1.1");
+
+ readonly RestRequest _request = new RestRequest("statuses/update.json", Method.Post)
+ .AddParameter("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
+ .AddParameter("include_entities", "true");
+
+ [Fact]
+ public void Adds_correct_signature() {
+ OAuth1Authenticator.AddOAuthData(_client, _request, _workflow, OAuthType.ProtectedResource, null);
+
+ var signature = _request.Parameters.First(x => x.Name == "oauth_signature").Value;
+ signature.Should().Be("hCtSmYh+iHYCEqBWrE7C7hYmtUk=");
+ }
+
+ [Fact]
+ public void Generates_correct_signature_base() {
+ const string method = "POST";
+
+ var requestParameters = _request.Parameters.ToWebParameters().ToArray();
+ var parameters = new WebPairCollection();
+ parameters.AddRange(requestParameters);
+ var url = _client.BuildUri(_request).ToString();
+ _workflow.RequestUrl = url;
+ var oauthParameters = _workflow.BuildProtectedResourceSignature(method, parameters);
+ oauthParameters.Parameters.AddRange(requestParameters);
+
+ var signatureBase = OAuthTools.ConcatenateRequestElements(method, url, oauthParameters.Parameters);
+
+ signatureBase.Should()
+ .Be(
+ "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521"
+ );
+ }
+}
\ No newline at end of file
diff --git a/test/RestSharp.Tests/Auth/OAuth1Tests.cs b/test/RestSharp.Tests/Auth/OAuth1Tests.cs
index 14b76c168..483a89771 100644
--- a/test/RestSharp.Tests/Auth/OAuth1Tests.cs
+++ b/test/RestSharp.Tests/Auth/OAuth1Tests.cs
@@ -42,7 +42,7 @@ public async Task Can_Authenticate_OAuth1_With_Querystring_Parameters() {
using var client = new RestClient(baseUrl);
var request = new RestRequest();
- var authenticator = OAuth1Auth.ForRequestToken(consumerKey, consumerSecret);
+ var authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret);
authenticator.ParameterHandling = OAuthParameterHandling.UrlOrPostParameters;
await authenticator.Authenticate(client, request);
@@ -74,22 +74,14 @@ public void Properly_Encodes_Parameter_Names(IList<(string, string)> parameters,
};
[Fact]
- public void Use_RFC_3986_Encoding_For_Auth_Signature_Base() {
- // reserved characters for 2396 and 3986
- // http://www.ietf.org/rfc/rfc2396.txt
- string[] reserved2396Characters = { ";", "/", "?", ":", "@", "&", "=", "+", "$", "," };
- // http://www.ietf.org/rfc/rfc3986.txt
- string[] additionalReserved3986Characters = { "!", "*", "'", "(", ")" };
-
- var reservedCharacterString = string.Join(
- string.Empty,
- reserved2396Characters.Union(additionalReserved3986Characters)
- );
-
- // act
- var escapedString = OAuthTools.UrlEncodeRelaxed(reservedCharacterString);
-
- // assert
- Assert.Equal("%3B%2F%3F%3A%40%26%3D%2B%24%2C%2521%252A%2527%2528%2529", escapedString);
+ public void Encodes_parameter() {
+ var parameter = new WebPair("status", "Hello Ladies + Gentlemen, a signed OAuth request!");
+ var parameters = new WebPairCollection { parameter };
+
+ const string expected = "status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521";
+
+ var norm = OAuthTools.NormalizeRequestParameters(parameters);
+ var escaped = OAuthTools.UrlEncodeRelaxed(norm);
+ escaped.Should().Be(expected);
}
}
\ No newline at end of file