I am building a twitter app for windows phone 7. But the twitter's OAuth is giving me a lot of problems. I am trying to get a Request Token and every single time I get the message: Failed to validate oauth signature and token. Any help would be appreciated.
here is my code:
Parameters base string:
public static string GetParameterString()
{
SystemParameters SysParameters = new SystemParameters();
Parameters Param = new Parameters();
StringBuilder sb = new StringBuilder();
sb.Append(SysParameters.CallbackUrl()).Append("=");
sb.Append(PercentEncoder(Param.callbackURL)).Append("&");
sb.Append(SysParameters.CosumerToken()).Append("=");
sb.Append(PercentEncoder(Param.consumerKey)).Append("&");
sb.Append(SysParameters.Nonce()).Append("=");
sb.Append(PercentEncoder(GetNonce())).Append("&");
sb.Append(SysParameters.SignatureMethod()).Append("=");
sb.Append(PercentEncoder("HMAC-SHA1")).Append("&");
sb.Append(SysParameters.TimeStamp()).Append("=");
sb.Append(PercentEncoder(GetTimeStamp().ToString())).Append("&");
sb.Append(SysParameters.OauthVersion()).Append("=");
sb.Append(PercentEncoder("1.0"));
return sb.ToString();
}
Signature Base string:
public static string GetSignatureBase()
{
SystemParameters SysParameters = new SystemParameters();
Parameters Param = new Parameters();
StringBuilder sb = new StringBuilder();
sb.Append("POST").Append("&");
sb.Append(PercentEncoder(SysParameters.RequestTokenURL())).Append("&");
sb.Append(PercentEncoder(GetParameterString()));
return sb.ToString();
}
Get signature:
public static string GetSignature()
{
SystemParameters SysParameters = new SystemParameters();
Parameters param = new Parameters();
string signature;
signature = Convert.ToBase64String((new HMACSHA1(Encoding.UTF8
.GetBytes(PercentEncoder(param.consumerSecret) + "&")))
.ComputeHash(Encoding.UTF8.GetBytes(GetSignatureBase())));
return PercentEncoder(signature);
}
get token:
private void button2_Click(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.Append(param.RequestURL)
.Append(OauthHelper.RequestType.RequesToken).Append("?");
sb.Append(sysParam.CallbackUrl()).Append("=");
sb.Append(OauthHelper.PercentEncoder(param.callbackURL)).Append("&");
sb.Append(sysParam.CosumerToken()).Append("=");
sb.Append(OauthHelper.PercentEncoder(param.consumerKey)).Append("&");
sb.Append(sysParam.Nonce()).Append("=");
sb.Append(OauthHelper.PercentEncoder(OauthHelper.GetNonce())).Append("&");
sb.Append(sysParam.Signature()).Append("=");
sb.Append(OauthHelper.PercentEncoder(OauthHelper.GetSignature())).Append("&");
sb.Append(sysParam.SignatureMethod()).Append("=");
sb.Append(OauthHelper.PercentEncoder("HMAC-SHA1")).Append("&");
sb.Append(sysParam.TimeStamp()).Append("=");
sb.Append(OauthHelper.PercentEncoder(OauthHelper.GetTimeStamp().ToString()))
.Append("&");
sb.Append(sysParam.OauthVersion()).Append("=");
sb.Append(OauthHelper.PercentEncoder("1.0"));
WebBrowserTask task = new WebBrowserTask();
task.URL = sb.ToString();
task.Show();
}
I have done this implementation referring another code.. my code goes something like this..
#region web query response methods
/// <summary>
/// Event that initiates QueryResponse for RequestToken request
/// </summary>
/// <param name="sender"></param>
/// <param name="e">Webresponse event argument</param>
void requestTokenQuery_QueryResponse(object sender, WebQueryResponseEventArgs e)
{
try
{
var parameters = TwitterHelperMethods.GetQueryParameters(e.Response);
OAuthTokenKey = parameters["oauth_token"];
tokenSecret = parameters["oauth_token_secret"];
var authorizeUrl = TwitterSettings.AuthorizeUri + "?oauth_token=" + OAuthTokenKey;
Dispatcher.BeginInvoke(() =>
{
this.browseTwitter.Navigate(new Uri(authorizeUrl));
});
}
catch (Exception ex)
{
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(ex.Message);
});
}
}
/// <summary>
/// Event that initiates QueryResponse for AccessToken request
/// </summary>
/// <param name="sender"></param>
/// <param name="e">Webresponse event argument</param>
void AccessTokenQuery_QueryResponse(object sender, WebQueryResponseEventArgs e)
{
try
{
var parameters = TwitterHelperMethods.GetQueryParameters(e.Response);
accessToken = parameters["oauth_token"];
accessTokenSecret = parameters["oauth_token_secret"];
userID = parameters["user_id"];
userScreenName = parameters["screen_name"];
TwitterHelperMethods.SetKeyValue<string>("AccessToken", accessToken);
TwitterHelperMethods.SetKeyValue<string>("AccessTokenSecret", accessTokenSecret);
TwitterHelperMethods.SetKeyValue<string>("ScreenName", userScreenName);
cacheManager.SaveToIsolatedStorage(Utilities.TwitterID, userID);
cacheManager.SaveToIsolatedStorage(Utilities.TwitterAccessToken, accessToken);
cacheManager.SaveToIsolatedStorage(Utilities.TwitterSecretAccessToken, accessTokenSecret);
// NavigationService.Navigate(new Uri("/HomePage.xaml", UriKind.RelativeOrAbsolute));
}
catch (Exception ex)
{
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(ex.Message);
});
}
}
#endregion
#region Browser events methods
/// <summary>
/// Called after the broswer is loaded
/// </summary>
/// <param name="sender">Browser</param>
/// <param name="e">Routed Event arguments</param>
private void browseTwitter_Loaded(object sender, RoutedEventArgs e)
{
accessToken = TwitterHelperMethods.GetKeyValue<string>("AccessToken");
accessTokenSecret = TwitterHelperMethods.GetKeyValue<string>("AccessTokenSecret");
userScreenName = TwitterHelperMethods.GetKeyValue<string>("ScreenName");
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(accessTokenSecret))
{
var requestTokenQuery = TwitterOAuthHelper.GetRequestTokenQuery();
requestTokenQuery.RequestAsync(TwitterSettings.RequestTokenUri, null);
requestTokenQuery.QueryResponse += new EventHandler<WebQueryResponseEventArgs>(requestTokenQuery_QueryResponse);
}
}
/// <summary>
/// Called when browser Initiates Navigation to Uri provided
/// </summary>
/// <param name="sender">Browser</param>
/// <param name="e">Navigating event arguments</param>
private void browseTwitter_Navigating(object sender, NavigatingEventArgs e)
{
if (e.Uri.ToString().StartsWith(TwitterSettings.CallbackUri))
{
cacheManager = new CacheManager();
var AuthorizeResult = TwitterHelperMethods.GetQueryParameters(e.Uri.ToString());
var VerifyPin = AuthorizeResult["oauth_verifier"];
//We now have the Verification pin
//Using the request token and verification pin to request for Access tokens
var AccessTokenQuery = TwitterOAuthHelper.GetAccessTokenQuery(
OAuthTokenKey, //The request Token
tokenSecret, //The request Token Secret
VerifyPin // Verification Pin
);
AccessTokenQuery.QueryResponse += new EventHandler<WebQueryResponseEventArgs>(AccessTokenQuery_QueryResponse);
AccessTokenQuery.RequestAsync(TwitterSettings.AccessTokenUri, null);
NavigationService.Navigate(new Uri("/OtherPages/HomePage.xaml", UriKind.RelativeOrAbsolute));
}
}
#endregion
My Twitterhelpermethods class goes like this
public class TwitterHelperMethods
{
public static Dictionary<string, string> GetQueryParameters(string response)
{
Dictionary<string, string> nameValueCollection = new Dictionary<string, string>();
string[] items = response.Split('&');
foreach (string item in items)
{
if (item.Contains("="))
{
string[] nameValue = item.Split('=');
if (nameValue[0].Contains("?"))
nameValue[0] = nameValue[0].Replace("?", "");
nameValueCollection.Add(nameValue[0], System.Net.HttpUtility.UrlDecode(nameValue[1]));
}
}
return nameValueCollection;
}
}
and twitteroauthhelper.cs is
public class TwitterOAuthHelper
{
public static OAuthWebQuery GetRequestTokenQuery()
{
var oauth = new OAuthWorkflow
{
ConsumerKey = TwitterSettings.consumerKey,
ConsumerSecret = TwitterSettings.consumerKeySecret,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
RequestTokenUrl = TwitterSettings.RequestTokenUri,
Version = TwitterSettings.oAuthVersion,
CallbackUrl = TwitterSettings.CallbackUri
};
var info = oauth.BuildRequestTokenInfo(WebMethod.Get);
var objOAuthWebQuery = new OAuthWebQuery(info);
objOAuthWebQuery.HasElevatedPermissions = true;
objOAuthWebQuery.SilverlightUserAgentHeader = "Hammock";
objOAuthWebQuery.SilverlightMethodHeader = "GET";
return objOAuthWebQuery;
}
public static OAuthWebQuery GetAccessTokenQuery(string requestToken, string RequestTokenSecret, string oAuthVerificationPin)
{
var oauth = new OAuthWorkflow
{
AccessTokenUrl = TwitterSettings.AccessTokenUri,
ConsumerKey = TwitterSettings.consumerKey,
ConsumerSecret = TwitterSettings.consumerKeySecret,
ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
Token = requestToken,
Verifier = oAuthVerificationPin,
Version = TwitterSettings.oAuthVersion
};
var info = oauth.BuildAccessTokenInfo(WebMethod.Post);
var objOAuthWebQuery = new OAuthWebQuery(info);
objOAuthWebQuery.HasElevatedPermissions = true;
objOAuthWebQuery.SilverlightUserAgentHeader = "Hammock";
objOAuthWebQuery.SilverlightMethodHeader = "GET";
return objOAuthWebQuery;
}
}
twittersettings.cs :-
public class TwitterSettings
{
public static string RequestTokenUri = "https://api.twitter.com/oauth/request_token";
public static string AuthorizeUri = "https://api.twitter.com/oauth/authorize";
public static string AccessTokenUri = "https://api.twitter.com/oauth/access_token";
public static string CallbackUri = "http://www.qwinixtech.com";
public static string StatusUpdateUrl { get { return "http://api.twitter.com"; } }
// #error TODO REGISTER YOUR APP WITH TWITTER TO GET YOUR KEYS AND FILL THEM IN HERE
public static string consumerKey = "Your consumer key here";
public static string consumerKeySecret = "Your consumer secret key here";
public static string AccessToken = "Your access token here";
public static string AccessTokenSecret = "Your Secret access token here";
public static string oAuthVersion = "1.0a";
}
This works perfectly well.. all u need to make this work is Hammock library.. I hope u already have it.. else pls download it..
Related
I am trying to implement an IExternalSignatureContainer (itext7) to sign PDF with belgian id card.
There is twa smartcard versions to deal with:
RSA/SHA256 signature
ECDSA/SHA384
I achieved to sign the pdf with the RSA/SHA256 siganture but I am not able to make the same for the ECDSA/SHA384.
Here is the Sign method of the ExternalSignatureContainer:
public byte[] Sign(Stream data)
{
var signCert = this.chain.First();
IssuerAndSerialNumber issuerAndSerialNumber = new IssuerAndSerialNumber(signCert.IssuerDN, signCert.SerialNumber);
var signerGenerator = new SignerInfoGeneratorBuilder().Build(
new BeidSignatureFactory(
(data) => this.Sign(data), // Sign data with the eid-middleware
() => this.GetHashAlgorithm(), // Get the hash algorithm of the id card from the eid-middleware
() => this.GetEncryptionAlgorithm() // Get the encryption algorithm of the id card from the eid-middleware
), signCert);
CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
gen.AddSignerInfoGenerator(signerGenerator);
IX509Store x509CertStore = X509StoreFactory.Create(
"Certificate/Collection",
new X509CollectionStoreParameters(this.chain));
gen.AddCertificates(x509CertStore);
CmsProcessableInputStream cmsProcessableInputStream = new CmsProcessableInputStream(data);
var sigData = gen.Generate(cmsProcessableInputStream, false);
return sigData.GetEncoded();
}
Here is a Custom ISignatureFactory and a custom ISigner that use the eid-middleware to access the begian eid card and use it to sign data.
internal class BeidSignatureFactory : ISignatureFactory
{
private readonly BeidSigner _signer = new BeidSigner();
private readonly BeidSigner.SignatureGenerator _signatureGenerator;
private readonly BeidSigner.AlgorithmGetter _getHashAlgorithm;
private readonly BeidSigner.AlgorithmGetter _getEncryptionAlgorithm;
public BeidSignatureFactory(BeidSigner.SignatureGenerator signatureGenerator, BeidSigner.AlgorithmGetter getHashAlgorithm, BeidSigner.AlgorithmGetter getEncryptionAlgorithm)
{
this._signatureGenerator = signatureGenerator;
this._getEncryptionAlgorithm = getEncryptionAlgorithm;
this._getHashAlgorithm = getHashAlgorithm;
}
public IStreamCalculator CreateCalculator()
{
var signer = new BeidSigner
{
Sign = this._signatureGenerator,
GetEncryptionAlgorithm = this._getEncryptionAlgorithm,
GetHashAlgorithm = this._getHashAlgorithm,
};
signer.Init(true, null);
return new DefaultSignatureCalculator(signer);
}
public object AlgorithmDetails
{
get
{
string encryptionAlgorithm = this._getEncryptionAlgorithm();
string hashAlgorithm = this._getHashAlgorithm();
switch (encryptionAlgorithm)
{
case "RSA":
return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption);
case "ECDSA":
return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384);
default:
return null;
}
}
}
}
internal class BeidSigner : ISigner
{
public delegate byte[] SignatureGenerator(byte[] data);
public delegate string AlgorithmGetter();
private byte[] _input;
public void Init(bool forSigning, ICipherParameters parameters)
{
this.Reset();
}
public void Update(byte input)
{
throw new NotImplementedException();
}
public void BlockUpdate(byte[] input, int inOff, int length)
{
this._input = input.Skip(inOff).Take(length).ToArray();
}
public byte[] GenerateSignature()
{
// This method doesn't exist, you will need to implement your own method here
return this.Sign(this._input);
}
public bool VerifySignature(byte[] signature)
{
throw new NotImplementedException();
}
public void Reset() { }
public string AlgorithmName
{
get
{
return $"{this.GetHashAlgorithm()}with{this.GetEncryptionAlgorithm()}";
}
}
public SignatureGenerator Sign { get; set; }
public AlgorithmGetter GetHashAlgorithm { get; set; }
public AlgorithmGetter GetEncryptionAlgorithm { get; set; }
}
Once the pdf signed with the EDCSA signature, the signature is not validated by acrobat reader "Document altered ro corrupted".
Perhaps I have completaly misunderstood the way tho sign the pdf.
Thanks for your help.
I have generated my APIclient Code using Nswagstudio as you can see :
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.6.1.0 (NJsonSchema v10.1.21.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
namespace MyNamespace
{
using System = global::System;
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.6.1.0 (NJsonSchema v10.1.21.0 (Newtonsoft.Json v12.0.0.0))")]
public partial interface IDefault1Client
{
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<Jwttoken> CreateTokenAsync();
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<Jwttoken> CreateTokenAsync(System.Threading.CancellationToken cancellationToken);
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<System.Collections.Generic.ICollection<string>> ReturnListAsync();
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<System.Collections.Generic.ICollection<string>> ReturnListAsync(System.Threading.CancellationToken cancellationToken);
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.6.1.0 (NJsonSchema v10.1.21.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class Default1Client : IDefault1Client
{
private string _baseUrl = "";
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
public Default1Client(string baseUrl)
{
BaseUrl = baseUrl;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
{
var settings = new Newtonsoft.Json.JsonSerializerSettings();
UpdateJsonSerializerSettings(settings);
return settings;
}
public string BaseUrl
{
get { return _baseUrl; }
set { _baseUrl = value; }
}
protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } }
partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<Jwttoken> CreateTokenAsync()
{
return CreateTokenAsync(System.Threading.CancellationToken.None);
}
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<Jwttoken> CreateTokenAsync(System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Default1/CreateToken");
var client_ = await CreateHttpClientAsync(cancellationToken).ConfigureAwait(false);
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = ((int)response_.StatusCode).ToString();
if (status_ == "200")
{
var objectResponse_ = await ReadObjectResponseAsync<Jwttoken>(response_, headers_).ConfigureAwait(false);
return objectResponse_.Object;
}
else
if (status_ != "200" && status_ != "204")
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null);
}
return default(Jwttoken);
}
finally
{
if (response_ != null)
response_.Dispose();
}
}
}
finally
{
if (client_ != null)
client_.Dispose();
}
}
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<System.Collections.Generic.ICollection<string>> ReturnListAsync()
{
return ReturnListAsync(System.Threading.CancellationToken.None);
}
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<string>> ReturnListAsync(System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Default1/ReturnList");
var client_ = await CreateHttpClientAsync(cancellationToken).ConfigureAwait(false);
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = ((int)response_.StatusCode).ToString();
if (status_ == "200")
{
var objectResponse_ = await ReadObjectResponseAsync<System.Collections.Generic.ICollection<string>>(response_, headers_).ConfigureAwait(false);
return objectResponse_.Object;
}
else
if (status_ != "200" && status_ != "204")
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null);
}
return default(System.Collections.Generic.ICollection<string>);
}
finally
{
if (response_ != null)
response_.Dispose();
}
}
}
finally
{
if (client_ != null)
client_.Dispose();
}
}
protected struct ObjectResponseResult<T>
{
public ObjectResponseResult(T responseObject, string responseText)
{
this.Object = responseObject;
this.Text = responseText;
}
public T Object { get; }
public string Text { get; }
}
public bool ReadResponseAsString { get; set; }
protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers)
{
if (response == null || response.Content == null)
{
return new ObjectResponseResult<T>(default(T), string.Empty);
}
if (ReadResponseAsString)
{
var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText, JsonSerializerSettings);
return new ObjectResponseResult<T>(typedBody, responseText);
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
}
}
else
{
try
{
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var streamReader = new System.IO.StreamReader(responseStream))
using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
{
var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
var typedBody = serializer.Deserialize<T>(jsonTextReader);
return new ObjectResponseResult<T>(typedBody, string.Empty);
}
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
}
}
}
private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value is System.Enum)
{
string name = System.Enum.GetName(value.GetType(), value);
if (name != null)
{
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
if (field != null)
{
var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
as System.Runtime.Serialization.EnumMemberAttribute;
if (attribute != null)
{
return attribute.Value != null ? attribute.Value : name;
}
}
return System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
}
}
else if (value is bool)
{
return System.Convert.ToString(value, cultureInfo).ToLowerInvariant();
}
else if (value is byte[])
{
return System.Convert.ToBase64String((byte[]) value);
}
else if (value != null && value.GetType().IsArray)
{
var array = System.Linq.Enumerable.OfType<object>((System.Array) value);
return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
}
return System.Convert.ToString(value, cultureInfo);
}
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class Jwttoken
{
[Newtonsoft.Json.JsonProperty("token", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Token { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.6.1.0 (NJsonSchema v10.1.21.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class ApiException : System.Exception
{
public int StatusCode { get; private set; }
public string Response { get; private set; }
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> Headers { get; private set; }
public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Exception innerException)
: base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + response.Substring(0, response.Length >= 512 ? 512 : response.Length), innerException)
{
StatusCode = statusCode;
Response = response;
Headers = headers;
}
public override string ToString()
{
return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
}
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.6.1.0 (NJsonSchema v10.1.21.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class ApiException<TResult> : ApiException
{
public TResult Result { get; private set; }
public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, TResult result, System.Exception innerException)
: base(message, statusCode, response, headers, innerException)
{
Result = result;
}
}
}
So I have added this code to my MVC core endpoint with these configurations
services.AddScoped<IDefault1Client>(provider =>
{
return new Default1Client("https://localhost:44381");
});
My apli using JWT as a security pattern.But myproblem is how can I pass my token into httpclient of the above code thatis generated by nswag studio .?
If i correct understand you
You have partial method
PrepareRequest
You can write header adding in this method
UPD:
partial void PrepareRequest(IHttpClient client, System.Net.Http.HttpRequestMessage request, string url)
{
var (name, value) = GetSecurityHeader();
request.Headers.Add(name, value);
}
Describe the bug
After upgrading from .net core 2.2 to 3.1, integration tests are failing.
All tests are wrapped in TransactionScope so that all changes to db should be revered (scope.Complete() is not called).
When call to the data access layer is made through api (HttpClient) records are created in the database, but they should not be since the entire test is wrapped in TransactionScope.
To Reproduce
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CustomDbContext : DbContext
{
private const string DefaultConnectionString = "Server=.;Initial Catalog=WebApi;Trusted_Connection=True;";
private readonly string _connectionString;
public CustomDbContext() : this(DefaultConnectionString)
{
}
public CustomDbContext(string connectionString)
{
_connectionString = connectionString;
}
public DbSet<Entity> Entities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new EntityConfiguration());
}
public async Task Save<TModel>(TModel model)
{
using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
{
Update(model);
await SaveChangesAsync();
scope.Complete();
}
}
}
public class EntityService : IEntityService
{
private readonly CustomDbContext _db;
public EntityService(CustomDbContext db)
{
_db = db;
}
public async Task Save(Entity model) => await _db.Save(model);
}
[ApiController]
[Route("[controller]")]
public class EntityController : ControllerBase
{
private readonly IEntityService _service;
public EntityController(IEntityService service)
{
_service = service;
}
[HttpPost]
public async Task<IActionResult> Save(Entity model)
{
await _service.Save(model);
return Ok();
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<CustomDbContext>();
services.AddScoped<IEntityService, EntityService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
/// <summary>
/// Apply this attribute to your test method to automatically create a <see cref="TransactionScope"/>
/// that is rolled back when the test is finished.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class AutoRollbackAttribute : BeforeAfterTestAttribute
{
TransactionScope scope;
/// <summary>
/// Gets or sets whether transaction flow across thread continuations is enabled for TransactionScope.
/// By default transaction flow across thread continuations is enabled.
/// </summary>
public TransactionScopeAsyncFlowOption AsyncFlowOption { get; set; } = TransactionScopeAsyncFlowOption.Enabled;
/// <summary>
/// Gets or sets the isolation level of the transaction.
/// Default value is <see cref="IsolationLevel"/>.Unspecified.
/// </summary>
public IsolationLevel IsolationLevel { get; set; } = IsolationLevel.Unspecified;
/// <summary>
/// Gets or sets the scope option for the transaction.
/// Default value is <see cref="TransactionScopeOption"/>.Required.
/// </summary>
public TransactionScopeOption ScopeOption { get; set; } = TransactionScopeOption.Required;
/// <summary>
/// Gets or sets the timeout of the transaction, in milliseconds.
/// By default, the transaction will not timeout.
/// </summary>
public long TimeoutInMS { get; set; } = -1;
/// <summary>
/// Rolls back the transaction.
/// </summary>
public override void After(MethodInfo methodUnderTest)
{
scope.Dispose();
}
/// <summary>
/// Creates the transaction.
/// </summary>
public override void Before(MethodInfo methodUnderTest)
{
var options = new TransactionOptions { IsolationLevel = IsolationLevel };
if (TimeoutInMS > 0)
options.Timeout = TimeSpan.FromMilliseconds(TimeoutInMS);
scope = new TransactionScope(ScopeOption, options, AsyncFlowOption);
}
}
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
private const string TestDbConnectionString = "Server=.;Initial Catalog=WebApiTestDB_V3;Trusted_Connection=True;";
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton(_ => new CustomDbContext(TestDbConnectionString));
var sp = services.BuildServiceProvider();
var db = sp.GetRequiredService<CustomDbContext>();
db.Database.Migrate();
});
}
}
public class IntegrationTest : IClassFixture<CustomWebApplicationFactory>
{
protected readonly HttpClient _client;
protected readonly IServiceProvider _serviceProvider;
protected readonly CustomDbContext _db;
public IntegrationTest(CustomWebApplicationFactory factory)
{
_client = factory.CreateClient();
_serviceProvider = factory.Services.CreateScope().ServiceProvider;
_db = _serviceProvider.GetRequiredService<CustomDbContext>();
}
protected void DetachAll()
{
_db.ChangeTracker.Entries()
.ToList()
.ForEach(e => e.State = EntityState.Detached);
}
protected async Task<Entity> AddTestEntity()
{
var model = new Entity
{
Name = "test entity"
};
await _db.AddAsync(model);
await _db.SaveChangesAsync();
return model;
}
}
public static class HttpContentHelper
{
public static HttpContent GetJsonContent(object model) =>
new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json");
}
[AutoRollback]
public class EntityIntegrationTest : IntegrationTest
{
private const string apiUrl = "/entity";
public EntityIntegrationTest(CustomWebApplicationFactory factory) : base(factory)
{
}
[Fact]
public async Task CanAdd()
{
// arrange
var model = new Entity
{
Name = "new entity"
};
var content = HttpContentHelper.GetJsonContent(model);
// act
var response = await _client.PostAsync(apiUrl, content);
// assert
response.EnsureSuccessStatusCode();
var result = await _db.Entities.FirstOrDefaultAsync();
Assert.Equal(model.Name, result.Name);
}
[Fact]
public async Task CanUpdate()
{
// arrange
var model = await AddTestEntity();
DetachAll(); // detach all entries because posting to api would create a new model, saving a new object with existing key throws entity already tracked exception
model.Name = "updated entity";
var content = HttpContentHelper.GetJsonContent(model);
// act
var response = await _client.PostAsync(apiUrl, content);
// assert
response.EnsureSuccessStatusCode();
var result = await _db.Entities.FirstOrDefaultAsync();
Assert.Equal(model.Id, result.Id);
Assert.Equal(model.Name, result.Name);
}
[Fact]
public async Task CannotInsertDuplicate()
{
// arrange
var entity = await AddTestEntity();
var model = new Entity
{
Name = entity.Name
};
var content = HttpContentHelper.GetJsonContent(model);
// act
var response = await _client.PostAsync(apiUrl, content);
// assert
var result = await response.Content.ReadAsStringAsync();
Assert.Contains("Cannot insert duplicate", result);
}
}
There are many files/classes involved so I've created a example repository
Example tests that are failing are in https://github.com/niksloter74/web-api-integration-test/tree/master/netcore3.1
Working example in .net core 2.2 https://github.com/niksloter74/web-api-integration-test/tree/master/netcore2.2
Direct test for service layer is working correctly
[AutoRollback]
public class EntityServiceTest : IntegrationTest
{
private readonly IEntityService service;
public EntityServiceTest(CustomWebApplicationFactory factory) : base(factory)
{
service = _serviceProvider.GetRequiredService<IEntityService>();
}
[Fact]
public async Task CanAdd()
{
// arrange
var model = new Entity
{
Name = "new entity"
};
// act
await service.Save(model);
// assert
var result = await _db.Entities.FirstOrDefaultAsync();
Assert.Equal(model.Name, result.Name);
}
[Fact]
public async Task CanUpdate()
{
// arrange
var model = await AddTestEntity();
model.Name = "updated entity";
// act
await service.Save(model);
// assert
var result = await _db.Entities.FirstOrDefaultAsync();
Assert.Equal(model.Id, result.Id);
Assert.Equal(model.Name, result.Name);
}
[Fact]
public async Task CannotInsertDuplicate()
{
// arrange
var entity = await AddTestEntity();
var model = new Entity
{
Name = entity.Name
};
// act
var ex = await Assert.ThrowsAnyAsync<Exception>(async () => await service.Save(model));
// assert
Assert.StartsWith("Cannot insert duplicate", ex.InnerException.Message);
}
}
This is by design but there’s a flag to get the old behavior back on TestServer called PreserveExecutionContext.
Here is an official discussion thread.
This line in IntegartionTest class fixed the problem
_factory.Server.PreserveExecutionContext = true;
I've also updated the repository
I'm having big trouble finding issue with the JWT token authentication with asp.net web api. This is first time I am dealing with JWT & Web Api authentication & Authorization.
I have implemented the following code.
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new OAuthTokenProvider(),
RefreshTokenProvider = new RefreshTokenProvider(),
AccessTokenFormat = new Provider.JwtFormat("http://localhost:49860")
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = "http://localhost:49860";
string audienceId = Config.AudienceId;
byte[] audienceSecret = TextEncodings.Base64Url.Decode(Config.AudienceSecret);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
}
OAuthTokenProvider.cs
public class OAuthTokenProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// validate client credentials (demo)
// should be stored securely (salted, hashed, iterated)
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
/***Note: Add User validation business logic here**/
if (context.UserName != context.Password)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{ "as:client_id", "Kaushik Thanki" }
});
ClaimsIdentity oAuthIdentity = new ClaimsIdentity("JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
}
}
JwtFormat.cs
public class JwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public JwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = Config.AudienceId;
string symmetricKeyAsBase64 = Config.AudienceSecret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
RefreshTokenProvider.cs
public class RefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
// maybe only create a handle the first time, then re-use for same client
// copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
//_refreshTokens.TryAdd(guid, context.Ticket);
_refreshTokens.TryAdd(guid, refreshTokenTicket);
// consider storing only the hash of the handle
context.SetToken(guid);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
}
Now Once I pass the authentication (Which I kept dummy for initial level matching same username & password) & got the token & refresh token.
When I request for method that is decorated with [Authorize] attribute, I always gets 401 status code.
I testing this method in postman following way
Any help or guidance will be really appreciated. I have invested my two days finding the solution for this but all in vain.
I have managed to integrate the predefined providers of Facebook, Twitter and Google. But I need to allow people to login via Paypal. I have created the following class that inherited off DotNetOpenAuth.AspNet.Clients.OAuth2Client.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;
using Validation;
using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.Messaging;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
namespace OauthClient
{
public class PayPalProviderClient : DotNetOpenAuth.AspNet.Clients.OAuth2Client
{
#region Oauth2
#region Constants and Fields
// https://www.x.com/developers/paypal/documentation-tools/quick-start-guides/oauth-openid-connect-integration-paypal#making-first-call-register
// 3 https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize?client_id=<your_app_id>&response_type=code&scope=openid&redirect_uri=https://www.example.com/my-redirect-uri
// 5 https://your-return-url?code=auth-code&scope=space-separted-scope-list&state=state
// 6 https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/tokenservice
private const string AuthorizationEndpoint = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize?response_type=code&scope=openid&";
private const string TokenEndpoint = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/tokenservice";
private const string UserDetailsEndPoint = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/userinfo";
private readonly string appId;
private readonly string appSecret;
#endregion
#region Constructors and Destructors
public PayPalProviderClient(string appId, string appSecret) : base("paypal")
{
Requires.NotNullOrEmpty(appId, "appId");
Requires.NotNullOrEmpty(appSecret, "appSecret");
this.appId = appId;
this.appSecret = appSecret;
}
#endregion
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var builder = new UriBuilder(AuthorizationEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
return builder.Uri;
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var builder = new UriBuilder(UserDetailsEndPoint);
builder.AppendQueryArgument("schema", "openid");
builder.AppendQueryArgument("access_token", accessToken);
Dictionary<string, string> userData = new Dictionary<string, string>();
using (WebClient client = new WebClient())
{
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
return null;
else
userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
}
return userData;
}
public override void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
base.RequestAuthentication(context, returnUrl);
}
private IDictionary<string, string> GetUserData(string accessCode, Uri redirectURI)
{
string token = QueryAccessToken(redirectURI, accessCode);
if (token == null || token == "")
return null;
return GetUserData(token);
}
private string QueryAccessToken(string returnUrl, string authorizationCode)
{
return QueryAccessToken(new Uri(returnUrl), authorizationCode);
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var builder = new UriBuilder(TokenEndpoint);
//"client_id=<your_app_id>&client_secret=<your_app_secret>&grant_type=authorization_code&code=<code>&redirect_uri=<urlencoded_return_url>"
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("client_secret", this.appSecret);
builder.AppendQueryArgument("grant_type", "authorization_code");
builder.AppendQueryArgument("code", authorizationCode);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
using (WebClient client = new WebClient())
{
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
return null;
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
if (userData.ContainsKey("access_token"))
return userData["access_token"].ToString();
else
return string.Empty;
}
}
private string RefreshAccessToken(Uri returnUrl, string authorizationCode)
{
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("client_secret", this.appSecret);
builder.AppendQueryArgument("grant_type", "refresh_token");
builder.AppendQueryArgument("code", authorizationCode);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
using (WebClient client = new WebClient())
{
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
return null;
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
if (userData.ContainsKey("access_token"))
return userData["access_token"].ToString();
else
return string.Empty;
}
}
#endregion
}
}
This is called by my controller
string callbackUrl = Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl });
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(callbackUrl);
Which results in "System.Collections.Generic.KeyNotFoundException was unhandled by user code" - The given key was not present in the dictionary.
Has anyone got a working example of using the DNOA with PayPal? Or know why I am getting this exception/what key am I missing?
UPDATE
I had managed to get a working version, see code below:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Web;
using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.Messaging;
using Newtonsoft.Json;
namespace OauthClients
{
/// <summary>
/// A DotNetOpenAuth client for logging in to paypal using OAuth2.
/// Reference: https://developers.paypal.com/accounts/docs/OAuth2
/// </summary>
public class PaypalOAuth2Client : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize?response_type=code&scope=openid&";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/tokenservice";
/// <summary>
/// The user info endpoint.
/// </summary>
private const string UserDetailsEndPoint = "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/userinfo";
/// <summary>
/// The base uri for scopes.
/// </summary>
private const string ScopeBaseUri = "https://www.paypal.com/webapps/auth/";
/// <summary>
/// The _app id.
/// </summary>
private readonly string _clientId;
/// <summary>
/// The _app secret.
/// </summary>
private readonly string _clientSecret;
/// <summary>
/// The requested scopes.
/// </summary>
private readonly string[] _requestedScopes;
#endregion
/// <summary>
/// Creates a new paypal OAuth2 client.
/// </summary>
/// <param name="clientId">The paypal Client Id</param>
/// <param name="clientSecret">The paypal Client Secret</param>
/// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
public PaypalOAuth2Client(string clientId, string clientSecret): base("paypal")
{
if (string.IsNullOrWhiteSpace(clientId))
throw new ArgumentNullException("clientId");
if (string.IsNullOrWhiteSpace(clientSecret))
throw new ArgumentNullException("clientSecret");
_clientId = clientId;
_clientSecret = clientSecret;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var builder = new UriBuilder(AuthorizationEndpoint);
builder.AppendQueryArgument("client_id", this._clientId);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
return builder.Uri;
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var request = WebRequest.Create(UserDetailsEndPoint + "?schema=openid&access_token=" + accessToken);
Dictionary<string, string> authData;
using (var response = request.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var streamReader = new StreamReader(responseStream))
{
authData = JsonConvert.DeserializeObject<Dictionary<string, string>>(streamReader.ReadToEnd());
return authData;
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgument("client_id", this._clientId);
builder.AppendQueryArgument("client_secret", this._clientSecret);
builder.AppendQueryArgument("grant_type", "authorization_code");
builder.AppendQueryArgument("code", authorizationCode);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
using (WebClient client = new WebClient())
{
var data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
return null;
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
if (userData.ContainsKey("access_token"))
return userData["access_token"].ToString();
else
return null;
}
}
public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
{
string code = context.Request.QueryString["code"];
string u = context.Request.Url.ToString();
if (string.IsNullOrEmpty(code))
return AuthenticationResult.Failed;
string accessToken = this.QueryAccessToken(returnPageUrl, code);
if (accessToken == null)
return AuthenticationResult.Failed;
IDictionary<string, string> userData = this.GetUserData(accessToken);
if (userData == null)
return AuthenticationResult.Failed;
string id = userData["user_id"];
string name = string.Empty ;
return new AuthenticationResult(
isSuccessful: true, provider: "PayPal", providerUserId: id, userName: name, extraData: userData);
}
}
}
You must provide dictionary entry with key="id" in your method GetUserData (dictionary keys with names "name" and "username" will do).
code from OAuth2Client.class
...
string id = userData["id"];
string name;
// Some oAuth providers do not return value for the 'username' attribute.
// In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id'
if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name)) {
name = id;
}
...
As you can see code will fail if one of those keys are missing.