I've been wrestling with the Twitter API for a few days now but I cannot post a message to an authenticated user's timeline. I've got an ASP.NET MVC 4 application that signs a user in via Twitter and saves the access token that comes back from the sign in process. That part works fine. I can see my application with read and write permissions within the authenticated user's twitter account.
I'm then using that access token, along with the consumer key, consumer secret and oauth token secret associated with my Twitter application, to post to the user's timeline. I'm getting a 401 unauthorised error every time. I've tried using the 1.1 API and the 1 API with the same result.
Most of the code comes from Gary Short's article here: http://garyshortblog.wordpress.com/2011/02/11/a-twitter-oauth-example-in-c/
Here's what I've got so far. If anyone can spot any clues as to what I'm missing I'd be most grateful.
public async Task<bool> Push(TwitterMessage twitterMessage)
{
const string updateApi = "http://api.twitter.com/1/statuses/update.json";
const string oauthConsumerKey = "<consumerKey>";
const string consumerSecret = "<consumerSecret>";
const string oauthSignatureMethod = "HMAC-SHA1";
const string oauthTokenSecret = "<tokenSecret>";
var signingKey = string.Format("{0}&{1}", consumerSecret.Escaped(), oauthTokenSecret.Escaped());
var postBody = "status=" + Uri.EscapeDataString(twitterMessage.MessageContent);
var oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
var oauthToken = "<authenticatedUserToken>";
var timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
var oauthTimestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString();
var message = string.Format("POST {0}?{1} HTTP/1.1", updateApi, postBody.Escaped());
var hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey));
var signatureString = Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(message)));
ServicePointManager.Expect100Continue = false;
var request = (HttpWebRequest)WebRequest.Create(updateApi);
request.KeepAlive = false;
var authorisationBuilder = new StringBuilder();
authorisationBuilder.Append("OAuth ");
authorisationBuilder.AppendFormat("oauth_consumer_key=\"{0}\",", oauthConsumerKey.Escaped());
authorisationBuilder.AppendFormat("oauth_signature_method=\"{0}\",", oauthSignatureMethod.Escaped());
authorisationBuilder.AppendFormat("oauth_timestamp=\"{0}\",", oauthTimestamp.Escaped());
authorisationBuilder.AppendFormat("oauth_nonce=\"{0}\",", oauthNonce.Escaped());
authorisationBuilder.AppendFormat("oauth_token=\"{0}\",", oauthToken.Escaped());
authorisationBuilder.AppendFormat("oauth_signature=\"{0}\"", signatureString.Escaped());
var authorisation = authorisationBuilder.ToString();
request.Headers.Add("Authorization", authorisation);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (var stream = await request.GetRequestStreamAsync())
{
var bodyBytes = new ASCIIEncoding().GetBytes(postBody);
stream.Write(bodyBytes, 0, bodyBytes.Length);
}
//Allow us a reasonable timeout in case Twitter's busy
request.Timeout = 3 * 60 * 1000;
try
{
var response = await request.GetResponseAsync() as HttpWebResponse;
return true;
}
catch (WebException)
{
return false;
}
}
public static string Escaped(this string input)
{
return Uri.EscapeDataString(input);
}
UPDATE Looking at this SO post it looks like I can't use the DotNetOpenAuth twitter client for authorisation, which I had been doing. The suggestion there is to extend the twitter consumer class instead to perform the authorisation, which will allow me to retrieve the user's token secret (the missing piece of my puzzle I think). Will post another update when I get this working.
Check this code and link/article simple and easy :
protected void btnTweet_Click(object sender, EventArgs e)
{
string oauthAccessToken = Session["twtoken"].ToString();
string oauthAccessTokenSecret = Session["twsecret"].ToString();
OAuthHelper oauthhelper = new OAuthHelper();
oauthhelper.TweetOnBehalfOf(oauthAccessToken, oauthAccessTokenSecret, txtTweet.Text);
if (string.IsNullOrEmpty(oauthhelper.oauth_error))
Response.Write("Twit Posted Successfully");
else
Response.Write(oauthhelper.oauth_error);
}
Read more how to get access token and secret key and download OAuthHelper and OAuthUtility Class below is the link -
How to post tweet on behalf of an user from asp.net using oauth authentication
Login with twitter using oauth authentication in asp.net and get access token, screen name and userid
So the problem is an issue with DotNetOpenAuth as it currently stands. For Twitter authentication, the DotNetOpenAuth client doesn't allow for the full authorisation flow (needed for posting to a user's timeline). Only the access token is retrieved from the initial handshake and not the access token secret. I was using the access token secret associated with my Twitter app, rather than the Twitter user who was signing in, so authorisation was failing every time.
UPDATE: I've finally gone with using Daniel Crenna's Tweetsharp library, which makes the code a little simpler than writing my own API wrapper would have been:
public async Task<bool> Push(TwitterAccount account)
{
var twitterService = new TwitterService(consumerKey, consumerSecret);
twitterService.AuthenticateWith(account.AccessToken, account.AccessTokenSecret);
var options = new SendTweetOptions {Status = string.Format("{0} {1}", account.Message.MessageContent, account.Message.ShortLink)};
var status = twitterService.SendTweet(options);
return status != null;
}
Related
Background
I have a back end application that has a Twitter app setup and I can query and pull user tweet/post data. This is great, however, right now on the front end I don't have full Twitter integration setup. What I mean by this is that on the front end the user can enter any Twitter username and I want to know for sure that the Twitter username entered actually belongs to the user. With a Twitter application key you can pull public Twitter data for any twitter account which works well for large scale data ingestion and in my case proof of concept kind of work. At the point I am now, I need to have the assumption enforced in the back end that the data being analyzed for a particular Twitter screen name is also owned by the user of the account on my web application.
The proposed Twitter Solution
Here is a bunch of reference documentation I have been trying to follow.
https://developer.twitter.com/en/docs/basics/authentication/guides/log-in-with-twitter
https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token
https://oauth.net/core/1.0/#anchor9
https://oauth.net/core/1.0/#auth_step1
I have been trying to follow this and I have had different permutations to the code posted below (one without the callback URL as parameters, one with etc.) but at this point, not very different. I have not had any success and it's been more than a couple of days, which is killing me.
The code
This is my attempt to follow the OAuth specification proposed above in the documentation. Note that this is ASP.NET Core 2.2 + code. Also, this is the code for just Step 1 in the Twitter guide for OAuth authentication and authorization.
public async Task<string> GetUserOAuthRequestToken()
{
int timestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(timestamp.ToString()));
string consumerKey = twitterConfiguration.ConsumerKey;
string oAuthCallback = twitterConfiguration.OAuthCallback;
string requestString =
twitterConfiguration.EndpointUrl +
OAuthRequestTokenRoute;
string parameterString =
$"oauth_callback={WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}&" +
$"oauth_consumer_key={twitterConfiguration.ConsumerKey}&" +
$"oauth_nonce={nonce}&" +
$"oauth_signature_method=HMAC_SHA1&" +
$"oauth_timestamp={timestamp}" +
$"oauth_version=1.0";
string signatureBaseString =
"POST&" +
WebUtility.UrlEncode(requestString) +
"&" +
WebUtility.UrlEncode(parameterString);
string signingKey =
twitterConfiguration.ConsumerSecret +
"&" + twitterConfiguration.AccessTokenSecret;
byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
byte[] signingKeyBytes = Encoding.ASCII.GetBytes(signingKey);
HMACSHA1 hmacSha1 = new HMACSHA1(signingKeyBytes);
byte[] signature = hmacSha1.ComputeHash(signatureBaseStringBytes);
string authenticationHeaderValue =
$"oauth_nonce=\"{nonce}\", " +
$"oauth_callback=\"{WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}\", " +
$"oauth_signature_method=\"HMAC_SHA1\", " +
$"oauth_timestamp=\"{timestamp}\", " +
$"oauth_consumer_key=\"{twitterConfiguration.ConsumerKey}\", " +
$"oauth_signature=\"{Convert.ToBase64String(signature)}\", " +
$"oauth_version=\"1.0\"";
HttpRequestMessage request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(
baseUri: new Uri(twitterConfiguration.EndpointUrl),
relativeUri: OAuthRequestTokenRoute);
request.Content = new FormUrlEncodedContent(
new Dictionary<string, string>() {
{ "oauth_callback", twitterConfiguration.OAuthCallback }
});
request.Headers.Authorization = new AuthenticationHeaderValue("OAuth",
authenticationHeaderValue);
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode)
{
return await httpResponseMessage.Content.ReadAsStringAsync();
}
else
{
return null;
}
}
Notes
I have tried to remove the callback URL from the parameters as well and that didn't work. I have tried all sort of slightly different permutations (urlencoded my signature, added the callback URL in the query string, removed it etc), but I have lost track at this point the one's I have tried and haven't (encodings, quotes etc.).
Ignore the fact that I am not serializing the response into a model yet as the goal is to first hit a success status code!
I have an integration test setup for this method as well and I keep getting 400 Bad Request with no additional information (which makes sense), but is absolutely not helping with debugging.
[Fact]
public async Task TwitterHttpClientTests_GetOAuthRequestToken_GetsToken()
{
var result = await twitterHttpClient.GetUserOAuthRequestToken();
Assert.NotNull(result);
}
As an aside I had some other questions as well:
Is there a way to verify a user's Twitter account without going
through the OAuth flow? The reason I ask this is because getting
through OAuth flow is proving to be difficult
Is it safe to do the first step of the Twitter login workflow on the back end and return the response to the front end? The response
would carry a sensitive token and token secret. (If I were to answer
this myself I would say you have to do it this way otherwise you
would have to hard code app secrets into front end configuration
which is worse). I ask this because this has been on my conscious
since I have started this and I'm worried a bit.
Is there an OAuth helper library for C# ASP.NET Core that can make this easier?
I solved this by writing unit tests and working through the Twitter documentation on Creating A Signature. Since that example provides keys and results, it's possible to verify that your code is correct.
Since you asked about libraries - I wrote LINQ to Twitter with the hope of helping others like myself with this difficult task.
In addition to to signature, the page navigation can be challenging as your code works through the OAuth flow. Please review the Twitter documentation on Obtaining user access tokens to understand this better. I've also documented this in the LINQ to Twitter Wiki on Securing your Applications. Here's how this will work with LINQ to Twitter:
First, I have an OAuthController with a Begin action to redirect a user to for kicking off the authentication process:
public async Task<ActionResult> Begin()
{
//var auth = new MvcSignInAuthorizer
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore(HttpContext.Session)
{
ConsumerKey = configuration["Twitter:ConsumerKey"],
ConsumerSecret = configuration["Twitter:ConsumerSecret"]
}
};
string twitterCallbackUrl = Request.GetDisplayUrl().Replace("Begin", "Complete");
return await auth.BeginAuthorizationAsync(new Uri(twitterCallbackUrl));
}
Notice that it's using an MvcSignInAuthorizer, passing in credentials via the CredentialStore property. If you were using your own raw code, you would be setting up the HTTP request with the Authorization header.
Next, notice that I'm modifying the current URL so that it will reference the same controller, but with the Complete endpoint. That is the oauth_callback that gets sent to Twitter authorization.
That process redirects the user to the Twitter web site, they authorize your app, and then it uses the oauth_callback to redirect the user back to your site. Here's how you handle that:
public async Task<ActionResult> Complete()
{
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore(HttpContext.Session)
};
await auth.CompleteAuthorizeAsync(new Uri(Request.GetDisplayUrl()));
// This is how you access credentials after authorization.
// The oauthToken and oauthTokenSecret do not expire.
// You can use the userID to associate the credentials with the user.
// You can save credentials any way you want - database,
// isolated storage, etc. - it's up to you.
// You can retrieve and load all 4 credentials on subsequent
// queries to avoid the need to re-authorize.
// When you've loaded all 4 credentials, LINQ to Twitter will let
// you make queries without re-authorizing.
//
//var credentials = auth.CredentialStore;
//string oauthToken = credentials.OAuthToken;
//string oauthTokenSecret = credentials.OAuthTokenSecret;
//string screenName = credentials.ScreenName;
//ulong userID = credentials.UserID;
//
return RedirectToAction("Index", "Home");
}
Again, you can see that I'm using MvcAuthorizer and completing the request. After completing the request, you'll be able to pull out the oauth_token and oauth_token_secret, as well as screen_name and user_id. You can save these artifacts and re-use them for all subsequent activity by this user, making their experience better because they don't have to log in every time you need to make a request.
On your question about verification, there is a Verify Credentials endpoint.
LINQ to Twitter has an ASP.NET Core Sample, API Samples with 100% API coverate, and full documentation on the Wiki if you want to learn more.
After hours and hours of going through the documentation I found the answer out. Turns out I missed some small details from the guides.
When making a request to oauth/request_token, when you sign the
request, you don't use the access token secret (for this specific request). Also, see the "Getting Signing Key" section of the signing a request guide and read the last few paragraphs. Therefore the signing key
does not have the access token secret
You must UrlEncode every single key and value. You must UrlEncode the authorization header as well.
I will post the updated code for you all here in case you need this in C#. Note that this code is not clean. You should separate OAuth functionality into some other class. This was my attempt to just get it to work.
public async Task<string> GetUserOAuthRequestToken()
{
int timestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(timestamp.ToString()));
string consumerKey = twitterConfiguration.ConsumerKey;
string oAuthCallback = twitterConfiguration.OAuthCallback;
string requestString =
twitterConfiguration.EndpointUrl +
OAuthRequestTokenRoute;
string parameterString =
$"oauth_callback={WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}&" +
$"oauth_consumer_key={WebUtility.UrlEncode(twitterConfiguration.ConsumerKey)}&" +
$"oauth_nonce={WebUtility.UrlEncode(nonce)}&" +
$"oauth_signature_method={WebUtility.UrlEncode(OAuthSigningAlgorithm)}&" +
$"oauth_timestamp={WebUtility.UrlEncode(timestamp.ToString())}&" +
$"oauth_version={WebUtility.UrlEncode("1.0")}";
string signatureBaseString =
"POST&" +
WebUtility.UrlEncode(requestString) +
"&" +
WebUtility.UrlEncode(parameterString);
string signingKey =
WebUtility.UrlEncode(twitterConfiguration.ConsumerSecret) +
"&";
byte[] signatureBaseStringBytes = Encoding.ASCII.GetBytes(signatureBaseString);
byte[] signingKeyBytes = Encoding.ASCII.GetBytes(signingKey);
HMACSHA1 hmacSha1 = new HMACSHA1(signingKeyBytes);
byte[] signature = hmacSha1.ComputeHash(signatureBaseStringBytes);
string base64Signature = Convert.ToBase64String(signature);
string authenticationHeaderValue =
$"oauth_nonce=\"{WebUtility.UrlEncode(nonce)}\", " +
$"oauth_callback=\"{WebUtility.UrlEncode(twitterConfiguration.OAuthCallback)}\", " +
$"oauth_signature_method=\"{WebUtility.UrlEncode(OAuthSigningAlgorithm)}\", " +
$"oauth_timestamp=\"{WebUtility.UrlEncode(timestamp.ToString())}\", " +
$"oauth_consumer_key=\"{WebUtility.UrlEncode(twitterConfiguration.ConsumerKey)}\", " +
$"oauth_signature=\"{WebUtility.UrlEncode(base64Signature)}\", " +
$"oauth_version=\"{WebUtility.UrlEncode("1.0")}\"";
HttpRequestMessage request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(
baseUri: new Uri(twitterConfiguration.EndpointUrl),
relativeUri: OAuthRequestTokenRoute);
request.Headers.Authorization = new AuthenticationHeaderValue("OAuth",
authenticationHeaderValue);
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode)
{
string response = await httpResponseMessage.Content.ReadAsStringAsync();
return response;
}
else
{
return null;
}
}
I am using TweetSharp to send tweets to users (currently testing it) however it keeps coming back with Bad Authentication Data
{"errors":[{"code":215,"message":"Bad Authentication data."}]}
I have checked my app settings and it has full read and write access. I have also tried to regenerate my consumer keys but still not luck.
here is my code
public ActionResult AccessToken()
{
string oauth_consumer_key = "<consumer key>";
string oauth_consumer_secret = "<consumer secret>";
var service = new TwitterService(oauth_consumer_key, oauth_consumer_secret);
// Now we need the Token and TokenSecret
OAuthRequestToken requestToken = service.GetRequestToken("http://localhost:37808/");
string authURL = service.GetAuthorizationUri(requestToken).ToString();
Process.Start(authURL);
SendTweetOptions options = new SendTweetOptions();
options.Status = "Hello there Twitter";
service.SendTweet(options);
var re = service.Response.Response;
return View();
}
Am I doing anything wrong?
Finally solved the issue and it works well. Based upon comments from Yort.
public ActionResult AccessToken()
{
// Step 1 - Retrieve an OAuth Request Token
TwitterService service = new TwitterService(ConfigurationManager.AppSettings["TwitterConsumerKey"], ConfigurationManager.AppSettings["TwitterConsumerSecret"]);
// This is the registered callback URL
OAuthRequestToken requestToken = service.GetRequestToken("http://localhost:37808/Twitter/OToken");
// Step 2 - Redirect to the OAuth Authorization URL
Uri uri = service.GetAuthorizationUri(requestToken);
return new RedirectResult(uri.ToString(), false /*permanent*/);
//return View();
}
public ActionResult OToken()
{
return View();
}
public ActionResult UserInfo(string oauth_token, string oauth_verifier)
{
var requestToken = new OAuthRequestToken { Token = oauth_token };
// Step 3 - Exchange the Request Token for an Access Token
TwitterService service = new TwitterService(ConfigurationManager.AppSettings["TwitterConsumerKey"],
ConfigurationManager.AppSettings["TwitterConsumerSecret"]);
OAuthAccessToken accessToken = service.GetAccessToken(requestToken, oauth_verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
TwitterUser user = service.VerifyCredentials(new VerifyCredentialsOptions());
ViewBag.Message = string.Format("{0}", user.ScreenName);
// Step 5 - Send Tweet to User TimeLine
SendTweetOptions options = new SendTweetOptions();
string URL = "file:\\C:\\Users\\<User>\\Desktop\\test.jpg";
string path = new Uri(URL).LocalPath;
// Sending with Media
using (var stream = new FileStream(path, FileMode.Open))
{
service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = "<status>",
Images = new Dictionary<string, Stream> { { path, stream } }
});
}
var responseText = service.Response.StatusCode;
if (responseText.ToString() == "OK")
{
ViewBag.Message = "Tweet Successful";
}
else
{
ViewBag.Message = "Tweet Unsuccessful";
}
return View();
}
}
I don't believe you can send Tweets as just a consumer, the Tweets have to be "owned" by a user account. You need to register a Twitter account, then do the full oauth authentication process to get an access token (in addition to the consumer token), then reauthorise the TweetSharp service using both tokens.
Your code above nearly gets there (I think). After the Process.start call there needs to be logic to use the verifier returned in the browser (a number displayed after the user logs in) to complete the auth process and act as that user. At the moment, your code gets half way through that process but does not complete it, so when you try to tweet your TweetSharp service is only authed as the app and not the user.
The originalTweetSharp readme.md does include the missing bits of code. Step 3 needs the actual verifier returned in the browser after login:
// Step 3 - Exchange the Request Token for an Access Token
string verifier = "123456"; // <-- This is input into your application by your user
OAuthAccessToken access = service.GetAccessToken(requestToken, verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(access.Token, access.TokenSecret);
//Now your tweet call should work here.
It also looks like you're doing this in a web app on the server? In which case you're using entirely the wrong oauth flow (I believe). This one is designed for desktop apps, hence the call that starts a new browser process for the user to login with. I'm not entirely sure how the web flow works as I've never used it, but I believe you need to redirect the user to the authorisation url you receive, and the callback registered with Twitter should point back to your site. I think there is some kind of state parameter that can be passed back through the oauth flow so you can implement your own logic to pickup where you left off based on a session id or similar.
I worked on this subject before. You have to developer account before the send tweet because you need tokens and keys. It's my windows service project.
I wrote my tokens and key codes in App.config
<appSettings>
<add key="twitterAccessToken" value="*****"/>
<add key="twitterAccessTokenSecret" value="*****"/>
<add key="twitterConsumerKey" value="*****"/>
<add key="twitterConsumerSecret" value="*****"/>
public static void SendTweet()
{
try
{
GetPixelImageFile();
string key = ConfigurationSettings.AppSettings.Get("twitterConsumerKey");
string secret = ConfigurationSettings.AppSettings.Get("twitterConsumerSecret");
string token = ConfigurationSettings.AppSettings.Get("twitterAccessToken");
string tokenSecret = ConfigurationSettings.AppSettings.Get("twitterAccessTokenSecret");
string message = "Color, Colorful, Pixel, Art, PixelColouring, Follow";
var service = new TweetSharp.TwitterService(key, secret);
service.AuthenticateWith(token, tokenSecret);
using (var stream = new FileStream(#"C:\Images\Pixel.png", FileMode.Open))
{
var result = service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = message,
Images = new Dictionary<string, Stream> { { "john", stream } }
});
SendMail("SendTweet", (result == null ? "" : result.Text));
}
}
catch (Exception ex)
{
SendMail("SendTweet", ex.Message);
}
}
The scenario is the following: I need to perform a federated authentication of a user (which uses his university account) into the Sharepoint site of his university and to obtain both the FedAuth and rtFa cookies (which I have to pass to SharePoint REST webservices in order to access resources).
I made some attempts but there is at least an issue in each one:
1) Using Microsoft.SharePoint.Client library
ClientContext context = new ClientContext(host);
SharePointOnlineCredentials creds = new SharePointOnlineCredentials(user, passw);
context.Credentials = creds;
Uri sharepointuri = new Uri(host);
string authCookie = creds.GetAuthenticationCookie(sharepointuri);
Web web = context.Web;
context.Load(web, w=>w.Lists);
context.ExecuteQuery();
fedAuthString = authCookie.Replace("SPOIDCRL=", string.Empty);
This way I manage to get the FedAuth cookie but I am unable to get the rtFa cookie.
How can I get the rtFa cookie at this point?
Can I intercept the HTTP request involved in such an operation (i.e., context.ExecuteQuery()) -- which presumably contains the rtFa cookie in the headers?
Or, can I get the rtFa cookie by only leveraging on the FedAuth cookie?
2) Using MsOnlineClaimsHelper
This is a helper class which can be found on the Internet (e.g., here http://blog.kloud.com.au/tag/msonlineclaimshelper/ ).
This class, as it is, works with normal authentication but fails with federated authentication.
So I adjusted it in order to make it work in this case.
As long as I understand, the steps are the following:
Authenticate using username and password to the STS ADFS service of the university (the "federated party" or the ISSUER) -- here the Relying Party is Sharepoint O365 STS ("https://login.microsoftonline.com/extSTS.srf")
If the auth succeeds, I get back a SAML assertion containing the claims and a security token
Now, I authenticate to the SharePoint site by passing the Security Token
If the token is recognized, I get back a response which contains the two cookies (FedAuth and rtFa)
I am not an expert in this matter, and I came out with the following code:
This is the code that calls the method above and try to get FedAuth and rtFa from credentials in two steps (step 1: get SAML token from Federated Party; step 2: pass token from Federated Party to Sharepoint):
private List<string> GetCookies(){
// 1: GET SAML XML FROM FEDERATED PARTY THE USER BELONGS TO
string samlToken = getResponse_Federation(sts: "https://sts.FEDERATEDDOMAIN.com/adfs/services/trust/13/usernamemixed/",
realm: "https://login.microsoftonline.com/extSTS.srf");
// 2: PARSE THE SAML ASSERTION INTO A TOKEN
var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers;
SecurityToken token = handlers.ReadToken(new XmlTextReader(new StringReader(samlToken )));
// 3: REQUEST A NEW TOKEN BASED ON THE ISSUED TOKEN
GenericXmlSecurityToken secToken = GetO365BinaryTokenFromToken(token);
// 4: NOW, EASY: I PARSE THE TOKEN AND EXTRACT FEDAUTH and RTFA
...............
}
private string getResponse_Federation(string stsUrl, string relyingPartyAddress)
{
var binding = new Microsoft.IdentityModel.Protocols.WSTrust.Bindings.UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);
binding.ClientCredentialType = HttpClientCredentialType.None;
var factory = new WSTrustChannelFactory(binding, stsUrl);
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "password";
factory.Credentials.SupportInteractive = false;
factory.TrustVersion = TrustVersion.WSTrust13;
IWSTrustChannelContract channel = null;
try
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(relyingPartyAddress), //("urn:sharepoint:MYFEDERATEDPARTY"),
ReplyTo = relyingPartyAddress,
KeyType = WSTrust13Constants.KeyTypes.Bearer,
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
RequestDisplayToken = true,
};
channel = (WSTrustChannel)factory.CreateChannel();
RequestSecurityTokenResponse response = null;
SecurityToken st = channel.Issue(rst, out response);
var genericToken = st as GenericXmlSecurityToken;
return genericToken.TokenXml.OuterXml;
}
catch (Exception e)
{
return null;
}
}
private GenericXmlSecurityToken GetO365BinaryTokenFromToken(SecurityToken issuedToken)
{
Uri u = new Uri("https://login.microsoftonline.com/extSTS.srf");
WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.IssuedToken;
Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory channel =
new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
binding, new EndpointAddress("https://login.microsoftonline.com/extSTS.srf"));
channel.TrustVersion = TrustVersion.WSTrust13;
channel.Credentials.SupportInteractive = false;
GenericXmlSecurityToken token = null;
try
{
RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer)
{
};
rst.AppliesTo = new EndpointAddress("urn:sharepoint:MYFEDERATEDPARTY");
channel.ConfigureChannelFactory();
var chan = (Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel)channel.CreateChannelWithIssuedToken(issuedToken);
RequestSecurityTokenResponse rstr = null;
token = chan.Issue(rst, out rstr) as GenericXmlSecurityToken;
return token;
}
catch (Exception ex){
Trace.TraceWarning("WebException in getO365BinaryTokenFromADFS: " + ex.ToString());
throw;
}
}
I managed to get back a SAML token from the university STS. However, when parsed, the resulting SecurityToken has no security keys (i.e., the SecurityKeys collection is empty)
With no keys, I get on GetO365BinaryTokenFromToken() but when I try to send the token to the SharePoint Authentication service -- I get the following error:
"The signing token Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken has no keys. The security token is used in a context that requires it to perform cryptographic operations, but the token contains no cryptographic keys. Either the token type does not support cryptographic operations, or the particular token instance does not contain cryptographic keys. Check your configuration to ensure that cryptographically disabled token types (for example, UserNameSecurityToken) are not specified in a context that requires cryptographic operations (for example, an endorsing supporting token)."
I think that there are also some configuration issues that I cannot control directly, on both sides (the university STS ADFS and the Sharepoint STS).
I hope that more expert people would bring clarity in this process and even provide advice to actually make this scenario work.
File download function
With the following function, I am able to download a file (given an URL such as https://myfederatedparty.sharepoint.com/sites/MYSITE/path/myfile.pdf) by issuing BOTH the FedAuth and the rtFa cookie. If I do not pass the rtFa cookie, I get an "Unauthorized" response.
public static async Task<byte[]> TryRawWsCall(String url, string fedauth, string rtfa, CancellationToken ct, TimeSpan? timeout = null) {
try {
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = new System.Net.CookieContainer();
CookieCollection cc = new CookieCollection();
cc.Add(new Cookie("FedAuth", fedauth));
cc.Add(new Cookie("rtFa", rtfa));
handler.CookieContainer.Add(new Uri(url), cc);
HttpClient _client = new HttpClient(handler);
if (timeout.HasValue)
_client.Timeout = timeout.Value;
ct.ThrowIfCancellationRequested();
var resp = await _client.GetAsync(url);
var result = await resp.Content.ReadAsByteArrayAsync();
if (!resp.IsSuccessStatusCode)
return null;
return result;
}
catch (Exception) { return null; }
}
In fact, only FedAuth cookie is mandatory when it comes to SharePoint Online/Office 365 authentication.
According to Remote Authentication in SharePoint Online Using Claims-Based Authentication:
The FedAuth cookies enable federated authorization, and the rtFA
cookie enables signing out the user from all SharePoint sites, even if
the sign-out process starts from a non-SharePoint site.
So, it is enough to provide SPOIDCRL HTTP header in order to perform authentication in SharePoint Online/Office 365, for example:
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
var credentials = new SharePointOnlineCredentials(userName,securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
request.Headers.Add(HttpRequestHeader.Cookie, authCookie);
The following examples demonstrates how to perform active authentication in SharePointOnline/Office 365 by providing FedAuth cookie.
Example 1: Retrieve FormDigest via SharePoint 2013 REST API (uisng MsOnlineClaimsHelper class)
public static string GetFormDigest(Uri webUri, string userName, string password)
{
var claimsHelper = new MsOnlineClaimsHelper(webUri, userName, password);
var endpointUri = new Uri(webUri,"/_api/contextinfo");
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
request.Method = WebRequestMethods.Http.Post;
request.Accept = "application/json;odata=verbose";
request.ContentType = "application/json;odata=verbose";
request.ContentLength = 0;
var fedAuthCookie = claimsHelper.CookieContainer.GetCookieHeader(webUri); //FedAuth are getting here
request.Headers.Add(HttpRequestHeader.Cookie, fedAuthCookie); //only FedAuth cookie are provided here
//request.CookieContainer = claimsHelper.CookieContainer;
using (var response = (HttpWebResponse) request.GetResponse())
{
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var content = streamReader.ReadToEnd();
var t = JToken.Parse(content);
return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
}
}
}
Example 2: Retrieve FormDigest via SharePoint 2013 REST API (using SharePointOnlineCredentials class)
public static string GetFormDigest(Uri webUri, string userName, string password)
{
var endpointUri = new Uri(webUri, "/_api/contextinfo");
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
request.Method = WebRequestMethods.Http.Post;
request.Accept = "application/json;odata=verbose";
request.ContentType = "application/json;odata=verbose";
request.ContentLength = 0;
var securePassword = new SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
request.Credentials = new SharePointOnlineCredentials(userName,securePassword);
using (var response = (HttpWebResponse)request.GetResponse())
{
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var content = streamReader.ReadToEnd();
var t = JToken.Parse(content);
return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
}
}
}
Update
The modified version of the example for downloading a file:
public static async Task<byte[]> DownloadFile(Uri webUri,string userName,string password, string relativeFileUrl, CancellationToken ct, TimeSpan? timeout = null)
{
try
{
var securePassword = new SecureString();
foreach (var c in password)
{
securePassword.AppendChar(c);
}
var credentials = new SharePointOnlineCredentials(userName, securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
var fedAuthString = authCookie.TrimStart("SPOIDCRL=".ToCharArray());
var cookieContainer = new CookieContainer();
cookieContainer.Add(webUri, new Cookie("SPOIDCRL", fedAuthString));
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookieContainer;
HttpClient _client = new HttpClient(handler);
if (timeout.HasValue)
_client.Timeout = timeout.Value;
ct.ThrowIfCancellationRequested();
var fileUrl = new Uri(webUri, relativeFileUrl);
var resp = await _client.GetAsync(fileUrl);
var result = await resp.Content.ReadAsByteArrayAsync();
if (!resp.IsSuccessStatusCode)
return null;
return result;
}
catch (Exception) { return null; }
}
I created a github project based on https://stackoverflow.com/users/1375553/vadim-gremyachev 's answer https://github.com/nddipiazza/SharepointOnlineCookieFetcher
with a project that can generate these cookies.
It has releases for Windows, Centos7 and Ubuntu16 and I used mono develop to build it so that it is platform independent.
Intended for users who are not making programs with CSOM in c# but still want to be able to easily get the cookies.
Usage
One Time Only Step: (see Access to the path "/etc/mono/registry" is denied)
sudo mkdir /etc/mono
sudo mkdir /etc/mono/registry
sudo chmod uog+rw /etc/mono/registry
Run program:
Linux: ./SharepointOnlineSecurityUtil -u youruser#yourdomain.com -w https://tenant.sharepoint.com
Windows: SharepointOnlineSecurityUtil.exe -u youruser#yourdomain.com -w https://tenant.sharepoint.com
Enter a password when promped
Result of stdout will have SPOIDCRL cookie.
I still needed both FedAuth and rtFa cookies for my purposes. I tried using just FedAuth, but it wouldn't work without both. Another developer confirmed he saw the same behavior.
NOTE: Legacy authentication must be enabled in your tenant for this to work.
Here is a thread to help obtain both FedAuth and rtFa.
Send Post request to https://login.microsoftonline.com/extSTS.srf with the following body.
Replace UserName, Password, EndPoint Address with relevant values.
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
<o:Security s:mustUnderstand="1"
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken>
<o:Username>[username]</o:Username>
<o:Password>[password]</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>[endpoint]</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
</t:RequestSecurityToken>
</s:Body>
</s:Envelope>
Note the content of the wsse:BinarySecurityToken node within the response data.
Send Post request to https://YourDomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0.
Replace 'YourDomain with relevant value. Provide the wsse:BinarySecurityToken content within the body of the request.
The response header will contain the FedAuth and rtFa cookies.
This is what I did. Might be useful to future readers!
For my usecase, I am running a WinForms Windows client side app. I was able to grab the FedAuth and rtFA cookie using a embedded WebBrowser Control.
I have uploaded my sample test project to github here: https://github.com/OceanAirdrop/SharePointOnlineGetFedAuthAndRtfaCookie
Here is what I did:
Step 01: Naviagte to SharePoint Url in WebBrowser Control
Using the WebBrowser control, first navigate to a web page on your sharepoint site that you have access to. It can be any page. The aim here is to get the cookies from the loaded page. This step only needs to be done once in the app.
webBrowser1.Navigate(#"https://xx.sharepoint.com/sites/xx/Forms/AllItems.aspx");
Step 02: Grab Cookies from WebBrowser Control
Next, override the Navigated event in the WebBrowser control. This lets you know the page has fully loaded.
Now, heres the wrinkle!! The FedAuth cookies are written with an HTTPOnly flag, which means they cannot be accessed from the .NET Framework. This means if you try to access the cookies of the WebBrowser control, you will get null string back!
// This line of code wont work and will return null
var cookies = webBrowser1.Document.Cookie;
So, to get around this, you instead need to call InternetGetCookieEx in the WININET.dll. I took the code from here. This is what the Navigated function handler looks like:
private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
try
{
if (webBrowser1.Url.AbsoluteUri == "about:blank")
return;
// This line calls through to InternetGetCookieEx
var cookieData = GetWebBrowserCookie.GetCookieInternal(webBrowser1.Url, false);
if (string.IsNullOrEmpty(cookieData) == false)
{
textBoxCookie.Text = cookieData;
var dict = ParseCookieData(cookieData);
textBoxFedAuth.Text = dict["FedAuth"];
textBoxrtFa.Text = dict["rtFa"];
}
}
catch (Exception)
{
}
}
Step 03: Progrmatically Make WebRequest Using Cookies
Now that we have the FedAuth and rtFA cookies we can continue on and use the HttpClient to call any andpoint we need. In my case calling many endpoints that contain images. The code looks like this:
private void buttonDownloadImage_Click(object sender, EventArgs e)
{
try
{
var url = $"https://xx.sharepoint.com/sites/xx/xx/Images/{textBoxImageName.Text}";
var handler = new HttpClientHandler();
handler.CookieContainer = new System.Net.CookieContainer();
// Add our cookies to collection
var cc = new CookieCollection();
cc.Add(new Cookie("FedAuth", textBoxFedAuth.Text));
cc.Add(new Cookie("rtFa", textBoxrtFa.Text));
handler.CookieContainer.Add(new Uri(url), cc);
var httpClient = new HttpClient(handler);
var resp = httpClient.GetAsync(url).Result;
var byteData = resp.Content.ReadAsByteArrayAsync().Result;
if (resp.IsSuccessStatusCode)
{
pictureBox1.Image = byteArrayToImage(byteData);
}
}
catch (Exception)
{
}
}
Thats it. And it works like a charm.
I am creating an app to get information from Fitbit.com using OAuth.
protected void btnConnect_Click(object sender, EventArgs e)
{
// Create OAuthService object, containing oauth consumer configuration
OAuthService service = OAuthService.Create(
new EndPoint(RequestTokenUrl, "POST"), // requestTokenEndPoint
new Uri(AuthorizationUrl), // authorizationUri
new EndPoint(AccessTokenUrl, "POST"), // accessTokenEndPoint
true, // useAuthorizationHeader
"http://app.fitbit.com", // realm
"HMAC-SHA1", // signatureMethod
"1.0", // oauthVersion
new OAuthConsumer(ConsumerKey, ConsumerSecret) // consumer
);
try
{
var personRepository = new PersonRepository();
var person = personRepository.GetPersonById(int.Parse(personSelect.SelectedItem.Value));
OAuthRequest request = OAuthRequest.Create(
new EndPoint(ProfileUrl, "GET"),
service,
this.Context.Request.Url,
//this.Context.Session.SessionID);
person.FitbitAuthAccessToken,
);
request.VerificationHandler = AspNetOAuthRequest.HandleVerification;
OAuthResponse response = request.GetResource();
// Check if OAuthResponse object has protected resource
if (!response.HasProtectedResource)
{
var token = new OAuthToken(TokenType.Request, person.FitbitAuthAccessToken,
person.FitbitAuthSecret, ConsumerKey);
// If not we are not authorized yet, build authorization URL and redirect to it
string authorizationUrl = service.BuildAuthorizationUrl(response.Token).AbsoluteUri;
Response.Redirect(authorizationUrl);
}
person.FitbitAuthAccessToken = response.Token.Token;
person.FitbitAuthSecret = response.Token.Secret;
person.PersonEncodedId = Doc["result"]["user"]["encodedId"].InnerText;
personRepository.Update(person);
// Store the access token in session variable
Session["access_token"] = response.Token;
}
catch (WebException ex)
{
Response.Write(ex.Message);
Response.Close();
}
catch (OAuthRequestException ex)
{
Response.Write(ex.Message);
Response.Close();
}
}
I save Fitbit Access Token and Secret in database.
How can I get information using just Access token and secret, without authorizing every time?
This would assume that the FitBit api was robust enough to not quire authentication every single time. I have seen API's implementing OAuth where you have an authentication process, then from there most of your calls simply require the AccessToken or secret. I would look at the method signatures for the service and see what types of parameters they are requiring.
If you look at the FitBit API about authentication and accessing resources, you will see that you just need to request the data you are interested in and add in the oAuth header with the access token. Here is what it should look like (from the API page):
GET /1/user/-/activities/date/2010-04-02.json HTTP/1.1
Host: api.fitbit.com
Authorization: OAuth realm="api.fitbit.com",
oauth_consumer_key="fitbit-example-client-application",
oauth_token="8d3221fb072f31b5ef1b3bcfc5d8a27a",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1270248088",
oauth_nonce="515379974",
oauth_signature="Gf5NUq1Pvg3DrtxHJyVaMXq4Foo%3D"
oauth_version="1.0"`
The base signature string will look like:
GET&http%3A%2F%2Fapi.fitbit.com%2F1%2Fuser%2F-%2Factivities%2Fdate%2F2010-04-02.json&oauth_consumer_key%3Dfitbit-example-client-application%26oauth_nonce%3D515379974%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1270248088%26oauth_token%3D8d3221fb072f31b5ef1b3bcfc5d8a27a%26oauth_version%3D1.0
I figured I'd offer my VerifyAuthenticationCore that is part of my FitbitClient that inherits from OAuthClient. It took me a while to get this working but I found that I was missing HttpDeliveryMethods.AuthorizationHeaderRequest when I was creating the web request. Adding this allowed the call to stop returning bad request (400) error messages.
The code below is basically using the user id and the access token to get the user profile information. All calls should basically work this way. All you would need to do is change the url and provide the id and token.
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
string username;
var accessToken = response.AccessToken;
var userId = response.ExtraData["encoded_user_id"];
var httpWebRequest = WebWorker.PrepareAuthorizedRequest(new MessageReceivingEndpoint(new Uri("http://api.fitbit.com/1/user/" + userId + "/profile.json"), HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), accessToken);
var dictionary = new Dictionary<string, string>();
dictionary.Add("accesstoken", accessToken);
dictionary.Add("link", "http://www.fitbit.com/user/" + userId);
using (var webResponse = httpWebRequest.GetResponse())
{
using (var stream = webResponse.GetResponseStream())
using (var reader = new StreamReader(stream))
{
var profile = JObject.Parse(reader.ReadToEnd())["user"];
dictionary.AddItemIfNotEmpty("name", profile["displayName"]);
dictionary.AddItemIfNotEmpty("pictureUrl", profile["avatar"]);
username = dictionary["name"];
}
}
return new AuthenticationResult(true, ProviderName, userId, username, dictionary);
}
I have this piece of code:
var settings = WebConfigurationManager.AppSettings;
var consumerKey = settings["Twitter.ConsumerKey"];
var consumerSecret = settings["Twitter.ConsumerSecret"];
var authToken = settings["Twitter.OAuthToken"];
var authVerifier = settings["Twitter.OAuthVerifier"];
//var accessToken = GetAccessToken(
// consumerKey, consumerSecret, authToken, string.Empty);
var tokens = new OAuthTokens()
{
AccessToken = authToken,
AccessTokenSecret = authVerifier,
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret
};
TwitterStatus.Update(tokens, txtComment.Text);
All I need it to to is update my twitter status. Unfortunately it is not working. It only worked once when I initially logged in to twitter to grant the application access. I then stored the authToken and authVerifier so I can reuse them for future updates.
Any idea what is wrong?
UPDATE: I just changed the code to :
TwitterResponse<TwitterStatus> tweetResponse = TwitterStatus.Update(tokens, txtComment.Text);
if (tweetResponse.Result == RequestResult.Success)
lblMessage.Text = "Twitter status successfully posted.";
else
lblMessage.Text = string.Format("Twitter status update failed with Error: '{0}'",
tweetResponse.ErrorMessage);
and I get an error message: "Invalid / expired token".
You are storing the wrong values. The authToken and verifier values need to be quickly exchanged for an access token using OAuthUtility.GetAccessToken(...). The access token that is returned from that method is what should be stored and supplied to Twitterizer.
-Ricky
The Twitterizer Author
I wanted to be able to make a simple status update from C#/.NET, but didn't want to embed a big library.
So I wrote a small OAuth.Manager class that does this stuff.
It's described here:
OAuth with Verification in .NET
Sample code to update status:
var oauth = new OAuth.Manager();
oauth["consumer_key"] = CONSUMER_KEY;
oauth["consumer_secret"] = CONSUMER_SECRET;
oauth["token"] = your_stored_access_token;
oauth["token_secret"] = your_stored_access_secret;
var url = "http://api.twitter.com/1/statuses/update.xml?status=Hello+World";
var authzHeader = oauth.GenerateAuthzHeader(url, "POST");
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.Headers.Add("Authorization", authzHeader);
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
MessageBox.Show("There's been a problem trying to tweet:" +
Environment.NewLine +
response.StatusDescription +
Environment.NewLine +
Environment.NewLine +
"You will have to tweet manually." +
Environment.NewLine);
}
For the first time through, you need to get an access token and secret. This is done in a multi-step process, starting with this code:
var oauth = new OAuth.Manager();
oauth["consumer_key"] = MY_APP_SPECIFIC_KEY;
oauth["consumer_secret"] = MY_APP_SPECIFIC_SECRET;
oauth.AcquireRequestToken("https://api.twitter.com/oauth/request_token", "POST");
Step 2 is to tell the user** to visit https://api.twitter.com/oauth/authorize?oauth_token=XXXX where xxxx is replaced with the actual token received, accessible in this case by oauth["token"]. Step 3 is to tell the user to grab (ctrl-c) the PIN from the webpage and paste it into your app, where you use the pin to get another type of token.
A better way is to automate that web UI sequence by using a Windows Form with an embedded WebBrowser control. When you set the Url property of that control to the appropriate value, it will show that webpage for you, inside the main form of your own app. You can also automate the part where you retrieve the PIN. This reduces context switches for your user and makes things simpler to understand.
Anyway, with the pin you do, step 4:
oauth.AcquireAccessToken("https://api.twitter.com/oauth/access_token",
"POST",
pin);
...which sends out another HTTP REST request, and when it returns you will have an accesss token and secret, available in oauth["token"] and oauth["token_secret"].
This authorization stuff with the web UI needs to happen only once; after you get the access token and secret once, you can store them and re-use them. They never expire, says Twitter.
You can then proceed to sending the status update...
var url = "http://api.twitter.com/1/statuses/update.xml?status=Hello+World";
var authzHeader = oauth.GenerateAuthzHeader(url, "POST");
...
...as above.
I know I am late to the game, but I created an end-to-end video tutorial showing exactly how to do this: I create an application on dev.twitter.com, install twitterizer using nuget, write the code to handle the oauth and finally write the code to use the access tokens received from twitter to make a tweet.
Video: http://www.youtube.com/watch?v=TGEA1sgMMqU
Tutorial: http://www.markhagan.me/Samples/Grant-Access-And-Tweet-As-Twitter-User-ASPNet
Code (in case you don't wan to leave this page):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Twitterizer;
namespace PostFansTwitter
{
public partial class twconnect : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var oauth_consumer_key = "YOUR_CONSUMER_KEY_HERE";
var oauth_consumer_secret = "YOUR_CONSUMER_SECRET_KEY_HERE";
if (Request["oauth_token"] == null)
{
OAuthTokenResponse reqToken = OAuthUtility.GetRequestToken(
oauth_consumer_key,
oauth_consumer_secret,
Request.Url.AbsoluteUri);
Response.Redirect(string.Format("http://twitter.com/oauth/authorize?oauth_token={0}",
reqToken.Token));
}
else
{
string requestToken = Request["oauth_token"].ToString();
string pin = Request["oauth_verifier"].ToString();
var tokens = OAuthUtility.GetAccessToken(
oauth_consumer_key,
oauth_consumer_secret,
requestToken,
pin);
OAuthTokens accesstoken = new OAuthTokens()
{
AccessToken = tokens.Token,
AccessTokenSecret = tokens.TokenSecret,
ConsumerKey = oauth_consumer_key,
ConsumerSecret = oauth_consumer_secret
};
TwitterResponse<TwitterStatus> response = TwitterStatus.Update(
accesstoken,
"Testing!! It works (hopefully).");
if (response.Result == RequestResult.Success)
{
Response.Write("we did it!");
}
else
{
Response.Write("it's all bad.");
}
}
}
}
}