Using OAuthWebSecurity with Salesforce - asp.net-mvc-4

I'm trying to get an ASP.NET MVC site to accept Salesforce as an authentication provider, but I am not having any luck. I'll start out with the IAuthenticationClient I have so far:
public class SalesForceOAuth2Client : OAuth2Client
{
private readonly String consumerKey;
private readonly String consumerSecret;
#if DEBUG
private const String BaseEndpoint = #"https://test.salesforce.com";
#else
private const String BaseEndpoint = #"https://login.salesforce.com";
#endif
private const String AuthorizeEndpoint = BaseEndpoint + #"/services/oauth2/authorize";
private const String TokenEndpoint = BaseEndpoint + #"/services/oauth2/token";
private const String RevokeEndpoint = BaseEndpoint + #"/services/oauth2/revoke";
public SalesForceOAuth2Client(String consumerKey, String consumerSecret)
: base("SalesForce")
{
if (String.IsNullOrWhiteSpace(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
if (String.IsNullOrWhiteSpace(consumerSecret))
{
throw new ArgumentNullException("consumerSecret");
}
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
String redirect_url = returnUrl.AbsoluteUri;
// Hack to work-around the __provider__ & __sid__ query parameters,
// but it is ultimately useless.
/*String state = String.Empty;
Int32 q = redirect_url.IndexOf('?');
if (q != -1)
{
state = redirect_url.Substring(q + 1);
redirect_url = redirect_url.Substring(0, q);
}*/
var builder = new UriBuilder(AuthorizeEndpoint);
builder.Query = "response_type=code"
+ "&client_id=" + HttpUtility.UrlEncode(this.consumerKey)
+ "&scope=full"
+ "&redirect_uri=" + HttpUtility.UrlEncode(redirect_url)
// Part of the above hack (tried to use `state` parameter)
/*+ (!String.IsNullOrWhiteSpace(state) ? "&state=" + HttpUtility.UrlEncode(state) : String.Empty)*/;
return builder.Uri;
}
protected override IDictionary<String, String> GetUserData(String accessToken)
{
// I am not sure how to get this yet as everything concrete I've
// seen uses the service's getUserInfo call (but this service relies
// heavily on a username, password, token combination. The whole point
// of using oatuh is to avoid asking the user for his/her credentials)
// more information about the original call:
// http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getuserinfo.htm
// Return static information for now
//TODO: Get information dynamically
return new Dictionary<String, String>
{
{ "username", "BradChristie" },
{ "name", "Brad Christie" }
};
}
protected override String QueryAccessToken(Uri returnUrl, String authorizationCode)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
using (StreamWriter streamWriter = new StreamWriter(webRequest.GetRequestStream()))
{
streamWriter.Write("grant_type=authorization_code");
streamWriter.Write("&client_id=" + HttpUtility.UrlEncode(this.consumerKey));
streamWriter.Write("&client_secret=" + HttpUtility.UrlEncode(this.consumerSecret));
streamWriter.Write("&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.AbsoluteUri));
streamWriter.Write("&code=" + HttpUtility.UrlEncode(authorizationCode));
streamWriter.Flush();
}
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
if (webResponse.StatusCode == HttpStatusCode.OK)
{
using (StreamReader streamReader = new StreamReader(webResponse.GetResponseStream()))
{
String response = streamReader.ReadToEnd();
var queryString = HttpUtility.ParseQueryString(response);
return queryString["access_token"];
}
}
return String.Empty;
}
}
The primary problem is that redirect_uri != Callback Url.
Salesforce enforces the callback URL you supply in the application configuration to match exactly to the value provided in redirect_uri of QueryAccessToken. Unfortunately OAuthWebSecurity relies on DotNetOpenAuth.AspNet, and that library appends two query parameters: __provider__ and __sid__. If I try to remove those (see the hack in GetServiceLoginUrl), obviously the login fails because the hand-back doesn't know how to continue on with the request without knowing which provider to use.
To work around this I did notice that the request call accepts an optional state parameter which is (essentially) there for passing things back and forth across the request/callback. However, with the dependence on __provider__ and __sid__ being their own keys having data=__provider__%3DSalesForce%26__sid__%3D1234567890 is useless.
Is there a work-around without having to fork/recompile the Microsoft.Web.WebPages.OAuth library and modify the OAuthWebSecurity.VerifyAuthenticationCore(HttpContextBase, String) method to look at data first, then continue on to OpenAuthSecurityMananer.GetProviderName?
Also, in case the registration mattered (AuthConfig.cs):
OAuthWebSecurity.RegisterClient(
new SalesForceOAuth2Client(/*consumerKey*/, /*consumerSecret*/),
"SalesForce",
new Dictionary<String, Object>()
);
Update (11.01.2013)
I just got a response back from Salesforce. It looks like they don't know how to implement 3.1.2 of the RFC which means that any query parameters you send in with the return_uri are not only ignored, but prohibited (at least when dynamic in nature). So, it looks like I can't use a library that works on every other platform and follows the standard--i have to create my own.
Sigh.

Related

Twitter new DM API, legacy method of authorization not working

We are trying to implement Twitter new DM API from Salesforce. We are sending our the JSON request in the body as mentioned in documentation but the legacy method for Oauth authorization is not working. Any help is greatly appreciated.
To add, I am sending a DM from salesforce to twitter, So
1) I am setting the request body in JSON.
2) I am doing a POST.
3) I am hitting the endpoint at 'https://api.twitter.com/1.1/direct_messages/events/new.json'
4) Oauth2, getting the access token(successfully)
5) Setting header as ('Content-Type', 'application/json').
6) Creating Authorization header as twitter mentions using consumer key, Nonce, Signature, Signature method, Timestamp, Version. Building the same as in "Guide" section of developer.twitter.com/en/docs/basics/authentication/guides/
7) On running the error code "{"errors":[{"code":32,"message":"Could not authenticate you."}]}".
Another important information that I had been using twitter old API to send DM that works perfect, only difference is it sends the request body in URL parameters instead of JSOn body but the authorization method remains same. As some new Functionality can only be achieved via Twitter New API and according to documentation the body needs to be sent via JSON format. Therefore the request part is changed but authorization is same.
Sample code:-
String accTok = 'redacted';
String conKey = 'redacted';
String conSec = 'redacted';
String accTokSec = 'redacted';
String theTweet = 'Hello world!';
String screenName ='some_test_username';
String jsonString = TwitterJsonReqGenerator.generateJSON(theTweet, screenName);
system.debug('JSON string ='+jsonString);
httpRequest newReq = new httpRequest();
newReq.setBody(jsonString);
newReq.setMethod('POST');
newReq.setEndpoint('https://api.twitter.com/1.1/direct_messages/events/new.json');
//Generate Nonce
string oAuth_nonce = EncodingUtil.base64Encode(blob.valueOf(string.valueOf(Crypto.getRandomInteger()+system.now().getTime())+string.valueOf(Crypto.getRandomInteger()))).replaceAll('[^a-z^A-Z^0-9]','');
map<String, String> heads = new map<String, String>{
'oauth_token'=>accTok,
'oauth_version'=>'1.0',
'oauth_nonce'=>oAuth_nonce,
'oauth_consumer_key'=>conKey,
'oauth_signature_method'=>'HMAC-SHA1',
'oauth_timestamp'=>string.valueOf(system.now().getTime()/1000)
};
//Alphabetize
string[] paramHeads = new string[]{};
paramHeads.addAll(heads.keySet());
paramHeads.sort();
string params = '';
for(String encodedKey : paramHeads){
params+=encodedKey+'%3D'+heads.get(encodedKey)+'%26';
}
//params+='status'+percentEncode('='+percentEncode(theTweet));
params+=percentEncode(theTweet);
//Build the base string
string sigBaseString = newReq.getMethod().toUpperCase()+'&'+EncodingUtil.urlEncode(newReq.getEndpoint(),'UTF-8')+'&'+params;
system.debug('signatureBaseString == '+sigBaseString);
//calculate signature
string sigKey = EncodingUtil.urlEncode(conSec,'UTF-8')+'&'+EncodingUtil.urlEncode(accTokSec,'UTF-8');
blob mac = crypto.generateMac('hmacSHA1', blob.valueOf(sigBaseString), blob.valueOf(sigKey));
string oauth_signature = EncodingUtil.base64Encode(mac);
heads.put(EncodingUtil.urlEncode('oauth_signature','UTF-8'), EncodingUtil.urlEncode(oauth_signature,'UTF-8'));
//build the authorization header
paramHeads.clear();
paramHeads.addAll(heads.keySet());
paramHeads.sort();
string oAuth_Body = 'OAuth ';
for(String key : paramHeads){
oAuth_Body += key+'="'+heads.get(key)+'", ';
}
oAuth_Body = oAuth_Body.subString(0, (oAuth_Body.length() - 2));
newReq.setHeader('Authorization', oAuth_Body);
system.debug('Authroization Header == '+oAuth_Body);
newReq.setHeader('Content-Type', 'application/json');
httpResponse httpRes = new http().send(newReq);
String response = httpRes.getBody();
system.debug(response);
Thanks
Prateek
I've written Twitter libraries and applications in the past, and the bst advice that I can give you is to use an existing implementation of OAuth instead of attempting to write your own. Re-implementing OAuth in new code is re-inventing the wheel, and it's a wheel that hates you. There are a number of robust and mature OAuth libraries that are free and/or open source.
Just happened to stumble on your query. I am posting a code(C#) (though it is a bit late) which worked for me to send DM to Twitter using the new API. Hope this helps. Thanks to Danny Tuppeny's blog
namespace TweetApp.Droid
{
class TweetDM
{
const string TwitterApiBaseUrl = "https://api.twitter.com/1.1/";
readonly string consumerKey, consumerKeySecret, accessToken, accessTokenSecret;
readonly HMACSHA1 sigHasher;
readonly DateTime epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public TweetDM(string consumerKey, string consumerKeySecret, string accessToken, string accessTokenSecret)
{
this.consumerKey = consumerKey;
this.consumerKeySecret = consumerKeySecret;
this.accessToken = accessToken;
this.accessTokenSecret = accessTokenSecret;
sigHasher = new HMACSHA1(new ASCIIEncoding().GetBytes(string.Format("{0}&{1}", consumerKeySecret, accessTokenSecret)));
}
public Task<string> Tweet(string text, string recipientID)
{
JSONObject jasonobject = new JSONObject
{
#event = new TwitterEvent
{
type = "message_create",
message_create = new msg_create
{
target = new tgt
{
recipient_id = recipientID
},
message_data = new msg_data
{
text = text
}
},
}
};
var JsonString =JsonConvert.SerializeObject(jasonobject);
var data4Auth = new Dictionary<string, string> {
};
return PrepareAuth("direct_messages/events/new.json", data4Auth, JsonString);
}
Task<string> PrepareAuth(string url, Dictionary<string, string> data4Auth, string JsonString)
{
var fullUrl = TwitterApiBaseUrl + url;
var timestamp = (int)((DateTime.UtcNow - epochUtc).TotalSeconds);
data4Auth.Add("oauth_consumer_key", consumerKey);
data4Auth.Add("oauth_signature_method", "HMAC-SHA1");
data4Auth.Add("oauth_timestamp", timestamp.ToString());
data4Auth.Add("oauth_nonce", "a"); // Required, but Twitter doesn't appear to use it, so "a" will do.
data4Auth.Add("oauth_token", accessToken);
data4Auth.Add("oauth_version", "1.0");
// Generate the OAuth signature and add it to our payload.
data4Auth.Add("oauth_signature", GenerateSignature(fullUrl, data4Auth));
// Build the OAuth HTTP Header from the data.
string oAuthHeader = GenerateOAuthHeader(data4Auth);
// Setting Content details
var JsonData = new StringContent(JsonString, Encoding.UTF8, "application/json");
return SendRequest(fullUrl, oAuthHeader, JsonData);
}
string GenerateSignature(string url, Dictionary<string, string> data)
{
var sigString = string.Join(
"&",
data
.Union(data)
.Select(kvp => string.Format("{0}={1}", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
var fullSigData = string.Format(
"{0}&{1}&{2}",
"POST",
Uri.EscapeDataString(url),
Uri.EscapeDataString(sigString.ToString())
);
return Convert.ToBase64String(sigHasher.ComputeHash(new ASCIIEncoding().GetBytes(fullSigData.ToString())));
}
string GenerateOAuthHeader(Dictionary<string, string> data)
{
return "OAuth " + string.Join(
", ",
data
.Where(kvp => kvp.Key.StartsWith("oauth_"))
.Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
}
async Task<string> SendRequest(string fullUrl, string oAuthHeader, StringContent jsondata)
{
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Add("Authorization", oAuthHeader);
var httpResp = await http.PostAsync(fullUrl, jsondata);
var respBody = await httpResp.Content.ReadAsStringAsync();
return respBody;
}
}
}
// Classes for creating JSON body
public class JSONObject
{
public TwitterEvent #event;
}
public class TwitterEvent
{
public string type;
public msg_create message_create;
}
public class msg_create
{
public tgt target;
public msg_data message_data;
}
public class tgt
{
public string recipient_id;
}
public class msg_data
{
public string text;
}
}
To call:
var twitter = new TweetDM(consumerKey, consumerKeySecret, accessToken, accessTokenSecret);
await twitter.Tweet(textBox1.Text, textBox2.Text);

Creating A Service Bus SAS Token and Consuming Relay in WinRT

I have a Service Bus Relay (WCF SOAP) I want to consume in my Windows Store App. I have written the code to create a token as well as the client which is below.
The problem is that I get an AuthorizationFailedFault returned with a faultstring "InvalidSignature: The token has an invalid signature." And I can't figure it out.
My Create Token method:
private static string CreateSasToken()
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970,1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
string stringToSign = webUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;
string hashKey = Encoding.UTF8.GetBytes(Secret).ToString();
MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,encoding);
IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding);
CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);
string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);
var sasToken = String.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
WebUtility.UrlEncode(signature), expiry, Issuer);
return sasToken;
}
My Client class:
public partial class ServiceClient
{
public async Task<string> GetDataUsingDataContract(string item, string sasToken)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("ServiceBusAuthorization",sasToken);
client.DefaultRequestHeaders.Add("SOAPAction",".../GetDataUsingDataContract");
client.DefaultRequestHeaders.Add("Host", "xxxxxxxxxxx.servicebus.windows.net");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,ServiceUri);
var content =new StringContent(#"<s:Envelope
xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Header></s:Header><s:Body>"+ item +#"</s:Body>
</s:Envelope>",System.Text.Encoding.UTF8,"application/xml");
request.Content = content;
HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
HttpContent stream = wcfResponse.Content;
var response = stream.ReadAsStringAsync();
var returnPacket = response.Result;
return returnPacket;
}
}
I have been successful consuming the Relay using Http (via Fiddler) by copying an unexpired token created by Micorosft.ServiceBus in a console app.
I figured out a solution which involved both methods being wrong.
CreateSasToken method:
A minor change involved setting the hashKey variable as byte[] and not string. This line:
string hashKey = Encoding.UTF8.GetBytes(Secret).ToString();
Changed to this:
var hashKey = Encoding.UTF8.GetBytes(Secret);
This change meant that I needed to use a different method to set keyBuffer.
This line:
IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding);
Change to this:
IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);
So the new CreateSasToken method is:
private static string GetSasToken()
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
string stringToSign = WebUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;
var hashKey = Encoding.UTF8.GetBytes(Secret);
MacAlgorithmProvider macAlgorithmProvider =
MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
const BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,
encoding);
IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);
CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);
string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);
var sasToken = String.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
WebUtility.UrlEncode(signature),
expiry, Issuer);
return sasToken;
}
Service Client Class
A couple of things to note here.
In order for the request to work, the SAS Token had to be added to the header as a parameter of a AuthenticationValueHeader object. So I added the following method to my helper class (ServiceBusHelper) which held the Key, KeyName and SasToken as properties and the CreateSasToken as a method.
public static AuthenticationHeaderValue CreateBasicHeader()
{
return new AuthenticationHeaderValue("Basic", SasToken);
}
The HttpRequestMessage Content property had to be created a special way. Taking the item parameter passed in, which was a serialized WCF DataContract type I needed to do a few things to make the SOAP envelope. Rather than go through them in detail here is the entire class (one method only). I will comment on the code to handle the response immediately following.
public partial class SalesNotifyServiceClient
{
public async Task<string> GetDataUsingDataContract(string item)
{
string returnPacket = "";
string element = "";
try
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("ServiceBusAuthorization",
ServiceBusHelper.CreateBasicHeader().Parameter);
client.DefaultRequestHeaders.Add("SOAPAction",
".../GetDataUsingDataContract");
client.DefaultRequestHeaders.Add("Host",
"xxxxxxxxxx.servicebus.windows.net");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
ServiceBusHelper.ServiceUri);
//Creating the request.Content
var encodedItem = item.Replace("<", "<").Replace(">", ">");
var strRequest =
#"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Header></s:Header><s:Body><GetDataUsingDataContract xmlns=
""http://www.xxxxxxxxxx.com/servicemodel/relay""><item>" +
encodedItem +
#"</item></GetDataUsingDataContract></s:Body></s:Envelope>";
var content = new StringContent(strRequest,
System.Text.Encoding.UTF8, "application/xml");
request.Content = content;
HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
HttpContent stream = wcfResponse.Content;
var response = await stream.ReadAsStringAsync();
//Handling the response
XDocument doc;
using (StringReader s = new StringReader(response))
{
doc = XDocument.Load(s);
}
if (doc.Root != null)
{
element = doc.Root.Value;
}
returnPacket = element;
}
catch (Exception e)
{
var message = e.Message;
}
return returnPacket;
}
}
In order to get at the DataContract object I had to do a few things to the response string. As you can see at the //Handling the response comment above, using StringReader I loaded the returned SOAP envelope as a string into an XDocument and the root value was my serialized DataContract object. I then deserialized the returnPacket variable returned from the method had my response object.

MvvmCross HTTP DownloadCache with authentication

In my app, the user need to be authenticated on the server to download data using WebAPIs.
The MvvmCross DownloadCache plugin seems to handle only basic HTTP GET queries. I can't add my authentication token in the url as it's a big SAML token.
How can I add a HTTP header to queries done through DownloadCache plugin ?
With the current version I think I should inject my own IMvxHttpFileDownloader but I'm looking for an easier solution. Injecting my own MvxFileDownloadRequest would be better (not perfect) but it doesn't have an interface...
I'm able to do it registering a custom IWebRequestCreate for a custom scheme (http-auth://).
It's a bit ugly to transform urls from my datasource but it does the job.
public class AuthenticationWebRequestCreate : IWebRequestCreate
{
public const string HttpPrefix = "http-auth";
public const string HttpsPrefix = "https-auth";
private static string EncodeCredential(string userName, string password)
{
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string credential = userName + ":" + password;
return Convert.ToBase64String(encoding.GetBytes(credential));
}
public static void RegisterBasicAuthentication(string userName, string password)
{
var authenticateValue = "Basic " + EncodeCredential(userName, password);
AuthenticationWebRequestCreate requestCreate = new AuthenticationWebRequestCreate(authenticateValue);
Register(requestCreate);
}
public static void RegisterSamlAuthentication(string token)
{
var authenticateValue = "SAML2 " + token;
AuthenticationWebRequestCreate requestCreate = new AuthenticationWebRequestCreate(authenticateValue);
Register(requestCreate);
}
private static void Register(AuthenticationWebRequestCreate authenticationWebRequestCreate)
{
WebRequest.RegisterPrefix(HttpPrefix, authenticationWebRequestCreate);
WebRequest.RegisterPrefix(HttpsPrefix, authenticationWebRequestCreate);
}
private readonly string _authenticateValue;
public AuthenticationWebRequestCreate(string authenticateValue)
{
_authenticateValue = authenticateValue;
}
public WebRequest Create(System.Uri uri)
{
UriBuilder uriBuilder = new UriBuilder(uri);
switch (uriBuilder.Scheme)
{
case HttpPrefix:
uriBuilder.Scheme = "http";
break;
case HttpsPrefix:
uriBuilder.Scheme = "https";
break;
default:
break;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri);
request.Headers[HttpRequestHeader.Authorization] = _authenticateValue;
return request;
}
}

Twitter API upgrade for Windows Phone

I have tweet poster in my application which uses oAuth 1.0 which will retire soon and will be non functional. I have to upgrade my API to 1.1. Twitter development center says that, If oAuth is used by your application, you can easily transaction to 1.1 by only updating your API endpoint. What exactly is API endpoint?
Here I'm having hard understanding about API endpoint. I think my asyncronous post call URL must be upgraded.
Here is the relevant codes which I think that might include the answer;
private void btnPostTweet_Click(object sender, RoutedEventArgs e)
{
namebocx.Text = userScreenName;
if (txtBoxNewTweet.Text.Trim().Length == 0) { return; }
var credentials = new OAuthCredentials
{
Type = OAuthType.ProtectedResource,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
ConsumerKey = TwitterSettings.consumerKey,
ConsumerSecret = TwitterSettings.consumerKeySecret,
Token = this.accessToken,
TokenSecret = this.accessTokenSecret,
Version = "1.0"
};
var restClient = new RestClient
{
Authority = TwitterSettings.StatusUpdateUrl,
HasElevatedPermissions = true,
Credentials = credentials,
Method = WebMethod.Post
};
restClient.AddHeader("Content-Type", "application/x-www-form-urlencoded");
// Create a Rest Request and fire it
var restRequest = new RestRequest
{
Path = "1/statuses/update.xml?status=" + txtBoxNewTweet.Text //Here must be endpoint of Api??
};
var ByteData = Encoding.UTF8.GetBytes(txtBoxNewTweet.Text);
restRequest.AddPostContent(ByteData);
restClient.BeginRequest(restRequest, new RestCallback(PostTweetRequestCallback));
}
}
and also here is the authentication settings:
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.google.com";
public static string StatusUpdateUrl { get { return "http://api.twitter.com"; } }
public static string consumerKey = "myconsumerkeyhere";
public static string consumerKeySecret = "myconsumersecrethere";
public static string oAuthVersion = "1.0a";
}
Here what twitter says me to replace with this instead of written in my code;
https://api.twitter.com/1.1/statuses/update.json
and some parameters told here -->> https://dev.twitter.com/docs/api/1.1/post/statuses/update
How should I update my API endpoint, what kind of changes do I have to do?
If you can help me, I really appreciate
You can change this:
Path = "1/statuses/update.xml?status=" + txtBoxNewTweet.Text
//Here must be endpoint of Api??
to this:
Path = "1.1/statuses/update.json?status=" + txtBoxNewTweet.Text
//Here must be endpoint of Api??

OAuth2 and DotNetOpenAuth - implementing Google custom client

I'm having an issue implementing custom OAuth2Client for google using DotNetOpenAuth and MVC4.
I've got to the point where I can successfully make the authorization request to the google endpoint
https://accounts.google.com/o/oauth2/auth
and Google asks if the user will allow my application access to their account. All good so far. When the user clicks 'OK', google then calls my callback URL as expected.
The problem is when I call the VerifyAuthentication method on the OAuthWebSecurity class (Microsoft.Web.WebPages.OAuth)
var authenticationResult = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
It's always returning an AuthenticationResult with IsSuccessful = false and Provider = ""
I've looked into the code for this, and the OAuthWebSecurity class tries to get the Provider name from
Request.QueryString["__provider__"]
but Google is not sending this information back in the querystring. The other provider I have implemented (LinkedIn) is sending the provider name back and it all works just fine.
I'm not sure what I can do from this point, apart from abandon the Microsoft.Web.WebPages.OAuth classes and just use DotNetOpenAuth without them, but I was hoping someone might have another solution I can try...
I've searched extensively, but can't seem to find anything to help ... I've found it really difficult even just to find examples of people doing the same thing, which has really surprised me.
Any help much appreciated!
Update: As Matt Johnson mentions below he has packaged up a solution to this which you can get from GitHub: https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2
As he notes:
DNOA and OAuthWebSecurity for ASP.Net MVC 4 ship with only an OpenId provider for Google. This is an OAuth2 client that you can use instead.
IMPORTANT - If you are using ASP.Net MVC 5, this package is not applicable. You should use Microsoft.Owin.Security.Google instead. (It also ships with the MVC 5 starter templates in VS 2013.)
I got round this in the end by catching the request when it comes in, and doing my own check to see which provider it has come from. Google allow you to send a parameter to the OAuth request called 'state', which they simply pass straight back to you when they make the callback, so I'm using this to pass the provider name for google, and I check for this in the absence of the "__provider__".
something like this:
public String GetProviderNameFromQueryString(NameValueCollection queryString)
{
var result = queryString["__provider__"];
if (String.IsNullOrWhiteSpace(result))
{
result = queryString["state"];
}
return result;
}
I've then implemented a custom OAuth2Client for Google, and I manually call the VerifyAuthentication method on that myself, bypassing the Microsoft wrapper stuff.
if (provider is GoogleCustomClient)
{
authenticationResult = ((GoogleCustomClient)provider).VerifyAuthentication(context, new Uri(String.Format("{0}/oauth/ExternalLoginCallback", context.Request.Url.GetLeftPart(UriPartial.Authority).ToString())));
}
else
{
authenticationResult = OAuthWebSecurity.VerifyAuthentication(returnUrl);
}
This has allowed me to keep the stuff I already had in place for the other providers using the Microsoft wrappers.
As requested by #1010100 1001010, here is my custom OAuth2Client for Google (NOTE: IT NEEDS SOME TIDYING! I HAVEN'T GOT ROUND TO TIDYING THE CODE UP YET. It does work though) :
public class GoogleCustomClient : OAuth2Client
{
ILogger _logger;
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/auth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token";
/// <summary>
/// The _app id.
/// </summary>
private readonly string _clientId;
/// <summary>
/// The _app secret.
/// </summary>
private readonly string _clientSecret;
#endregion
public GoogleCustomClient(string clientId, string clientSecret)
: base("Google")
{
if (string.IsNullOrWhiteSpace(clientId)) throw new ArgumentNullException("clientId");
if (string.IsNullOrWhiteSpace(clientSecret)) throw new ArgumentNullException("clientSecret");
_logger = ObjectFactory.GetInstance<ILogger>();
this._clientId = clientId;
this._clientSecret = clientSecret;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
StringBuilder serviceUrl = new StringBuilder();
serviceUrl.AppendFormat("{0}?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile", AuthorizationEndpoint);
serviceUrl.Append("&state=google");
serviceUrl.AppendFormat("&redirect_uri={0}", returnUrl.ToString());
serviceUrl.Append("&response_type=code");
serviceUrl.AppendFormat("&client_id={0}", _clientId);
return new Uri(serviceUrl.ToString());
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
RestClient client = new RestClient("https://www.googleapis.com");
var request = new RestRequest(String.Format("/oauth2/v1/userinfo?access_token={0}", accessToken), Method.GET);
IDictionary<String, String> extraData = new Dictionary<String, String>();
var response = client.Execute(request);
if (null != response.ErrorException)
{
return null;
}
else
{
try
{
var json = JObject.Parse(response.Content);
string firstName = (string)json["given_name"];
string lastName = (string)json["family_name"];
string emailAddress = (string)json["email"];
string id = (string)json["id"];
extraData = new Dictionary<String, String>
{
{"accesstoken", accessToken},
{"name", String.Format("{0} {1}", firstName, lastName)},
{"firstname", firstName},
{"lastname", lastName},
{"email", emailAddress},
{"id", id}
};
}
catch(Exception ex)
{
_logger.Error("Error requesting OAuth user data from Google", ex);
return null;
}
return extraData;
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
StringBuilder postData = new StringBuilder();
postData.AppendFormat("client_id={0}", this._clientId);
postData.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(returnUrl.ToString()));
postData.AppendFormat("&client_secret={0}", this._clientSecret);
postData.AppendFormat("&grant_type={0}", "authorization_code");
postData.AppendFormat("&code={0}", authorizationCode);
string response = "";
string accessToken = "";
var webRequest = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
try
{
using (Stream s = webRequest.GetRequestStream())
{
using (StreamWriter sw = new StreamWriter(s))
sw.Write(postData.ToString());
}
using (WebResponse webResponse = webRequest.GetResponse())
{
using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
{
response = reader.ReadToEnd();
}
}
var json = JObject.Parse(response);
accessToken = (string)json["access_token"];
}
catch(Exception ex)
{
_logger.Error("Error requesting OAuth access token from Google", ex);
return null;
}
return accessToken;
}
public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
{
string code = context.Request.QueryString["code"];
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["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;
}
// add the access token to the user data dictionary just in case page developers want to use it
userData["accesstoken"] = accessToken;
return new AuthenticationResult(
isSuccessful: true, provider: this.ProviderName, providerUserId: id, userName: name, extraData: userData);
}
You can add a provider query parameter to the end of your callback url.
e.g. https://mywebsite.com/Account/ExternalLoginCallback?provider=google
The you will get it and you don't need the work around.