Click here to Skip to main content
11,702,130 members (62,317 online)
Click here to Skip to main content

Twitter OAuth authentication using .NET

, 26 Aug 2014 CPOL 121.6K 6.1K 45
Rate this:
Please Sign up or sign in to vote.
A example showing how to authenticate a Twitter application using oAuth and the application access token.

Introduction

In this article, I want to demonstrate how to implement OAuth authentication in .NET. I've previously written about my dislike of third party SDKs for social media integration and how we should leverage technology based solutions instead. One of the sticking points in doing this tends to be that implementing OAuth based authentication is relatively difficult compared with actually making the requests themselves. There is documentation available, but there seems to be a lack of .NET example code to go with it.

In keeping with my thoughts in previous articles, I would recommend using open source OAuth based libraries to solve this problem, and again avoid resorting to third party Twitter/Facebook implementations which more strongly couple code to specific APIs. This keeps the solution more reusable and builds on specific technologies to better future proof your application.

I've also previously shown how client-side plug-ins can be used in combination with server-side code to speed development in this area. However, sometimes authentication does need to be implemented purely on the server-side.

So how difficult is this?

It turns out implementing OAuth on the server-side in .NET isn't too difficult, the battle is getting the encoding and authentication signature right. With so few examples, it can be a little daunting, so here's an example written in pure .NET using the official Twitter OAuth documentation and a bit of trial and error.

Background

The following example shows how to authenticate against the Twitter APIs using a registered Twitter application. Any interaction with the APIs when authenticated in this manner will behave as if coming from the Twitter account under which the application has been registered. It's therefore useful for sending out status updates or sending out notifications from a specific account.

Usually OAuth requires redirecting the user to a login screen to obtain an oAuth token which requires a bit more work. However, when authenticating via a Twitter application, this step is skipped as your application already has an oAuth token provided (access token). Whether you are using the application oAuth token or a user oAuth token, the following code can be used to authenticate against the Twitter APIs.

The Code

The first step is to visit the Twitter developer section and register a new application. On completion, you will be provided with a set of public/private keys which you will need the replace in the example below in order to run. The values I have used directly correspond with the documented example here. Make sure you replace them with your own.

var oauth_token           = "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw";
var oauth_token_secret    = "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA";
var oauth_consumer_key    = "GDdmIQH6jhtmLUypg82g";
var oauth_consumer_secret = "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98";

We also need to define some details about the request. This includes a unique oauth_nonce parameter which must be generated per request, and a timestamp.

var oauth_version          = "1.0";
var oauth_signature_method = "HMAC-SHA1";
var oauth_nonce            = Convert.ToBase64String(
                                  new ASCIIEncoding().GetBytes(
                                       DateTime.Now.Ticks.ToString()));
var timeSpan               = DateTime.UtcNow
                                  - new DateTime(1970, 1, 1, 0, 0, 0, 0,
                                       DateTimeKind.Utc);
var oauth_timestamp        = Convert.ToInt64(timeSpan.TotalSeconds).ToString();
var resource_url           = "https://api.twitter.com/1.1/statuses/update.json";
var status                 = "Updating status via REST API if this works";

The next step is to generate an encrypted oAuth signature which Twitter will use to validate the request. To do this, all of the request data is concatenated into a particular format as follows.

var baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}&status={6}";

var baseString = string.Format(baseFormat,
                            oauth_consumer_key,
                            oauth_nonce,
                            oauth_signature_method,
                            oauth_timestamp,
                            oauth_token,
                            oauth_version,
                            Uri.EscapeDataString(status)
                            );

baseString = string.Concat("POST&", Uri.EscapeDataString(resource_url), 
             "&", Uri.EscapeDataString(baseString));

Using this base string, we then encrypt the data using a composite of the secret keys and the HMAC-SHA1 algorithm.

var compositeKey = string.Concat(Uri.EscapeDataString(oauth_consumer_secret),
                        "&",  Uri.EscapeDataString(oauth_token_secret));

string oauth_signature;
using (HMACSHA1 hasher = new HMACSHA1(ASCIIEncoding.ASCII.GetBytes(compositeKey)))
{
    oauth_signature = Convert.ToBase64String(
        hasher.ComputeHash(ASCIIEncoding.ASCII.GetBytes(baseString)));
}

The oAuth signature is then used to generate the Authentication header. This requires concatenating the public keys and the token generated above into the following format.

var headerFormat = "OAuth oauth_nonce=\"{0}\", oauth_signature_method=\"{1}\", " +
                   "oauth_timestamp=\"{2}\", oauth_consumer_key=\"{3}\", " +
                   "oauth_token=\"{4}\", oauth_signature=\"{5}\", " +
                   "oauth_version=\"{6}\"";

var authHeader = string.Format(headerFormat,
                        Uri.EscapeDataString(oauth_nonce),
                        Uri.EscapeDataString(oauth_signature_method),
                        Uri.EscapeDataString(oauth_timestamp),
                        Uri.EscapeDataString(oauth_consumer_key),
                        Uri.EscapeDataString(oauth_token),
                        Uri.EscapeDataString(oauth_signature),
                        Uri.EscapeDataString(oauth_version)
                );

We are now ready to send the request, which is the easy part. Note, we must also disable the Expect: 100-Continue header using the ServicePointManager. Without this code, .NET sends the header by default, which is not supported by Twitter.

var postBody = "status=" + Uri.EscapeDataString(status);

ServicePointManager.Expect100Continue = false;

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(resource_url);
request.Headers.Add("Authorization", authHeader);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (Stream stream = request.GetRequestStream())
{
    byte[] content = ASCIIEncoding.ASCII.GetBytes(postBody);
    stream.Write(content, 0, content.Length);
}
WebResponse response = request.GetResponse();

Summary

This example hopefully shows how OAuth can be implemented with fairly little effort. In the example provided, I've kept everything inline for clarity; however, in the real world, you would obviously refactor the code into more sensible layers.

In this way, it's possible to build some highly testable lightweight classes in order to generate the required message signature, make the requests, and handle the response.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

TheCodeKing
Architect
United Kingdom United Kingdom
Mike Carlisle - Technical Architect with over 15 years experience in a wide range of technologies.

@TheCodeKing

You may also be interested in...

Comments and Discussions

 
QuestionThank~ TheCodeKing Pin
Member 1039050520-May-15 4:31
memberMember 1039050520-May-15 4:31 
Questiongetting error Pin
Member 1061473024-Mar-15 7:32
memberMember 1061473024-Mar-15 7:32 
QuestionDoesnot work Pin
Member 140823826-Jan-15 20:05
memberMember 140823826-Jan-15 20:05 
GeneralMy vote of 5 Pin
Thomas Maierhofer (Tom)26-Jan-15 6:09
memberThomas Maierhofer (Tom)26-Jan-15 6:09 
QuestionCan not tweet more then 2 consecutive tweets in a row. Pin
Member 1128979818-Dec-14 6:05
memberMember 1128979818-Dec-14 6:05 
QuestionRe: Can not tweet more then 2 consecutive tweets in a row. Pin
Member 1154959528-Mar-15 19:50
memberMember 1154959528-Mar-15 19:50 
AnswerRe: Can not tweet more then 2 consecutive tweets in a row. Pin
Member 1154959529-Mar-15 2:56
memberMember 1154959529-Mar-15 2:56 
AnswerRe: Can not tweet more then 2 consecutive tweets in a row. Pin
Member 1128979829-Mar-15 3:05
memberMember 1128979829-Mar-15 3:05 
GeneralRe: Can not tweet more then 2 consecutive tweets in a row. Pin
dance2die14-Aug-15 5:06
memberdance2die14-Aug-15 5:06 
QuestionError 401 Pin
William Costa Rodrigues13-Nov-14 15:00
professionalWilliam Costa Rodrigues13-Nov-14 15:00 
Questionheader format change Pin
Juanjo luvonovich25-Oct-14 3:57
memberJuanjo luvonovich25-Oct-14 3:57 
QuestionError:The remote server returned an error: (403) Forbidden. Pin
FeroseKhan23-Sep-14 20:08
memberFeroseKhan23-Sep-14 20:08 
AnswerRe: Error:The remote server returned an error: (403) Forbidden. Pin
Member 1006531319-Mar-15 16:43
memberMember 1006531319-Mar-15 16:43 
Question401 hatası alıyorum. I am taking still 401 error. Pin
Member 96148813-Sep-14 1:45
memberMember 96148813-Sep-14 1:45 
QuestionTo Make it work just make two changes Pin
DarkNetMaster17-Apr-14 17:49
memberDarkNetMaster17-Apr-14 17:49 
AnswerRe: To Make it work just make two changes Pin
Member 1103647825-Aug-14 22:09
memberMember 1103647825-Aug-14 22:09 
AnswerRe: To Make it work just make two changes Pin
Manuel M.N.25-Aug-14 22:12
memberManuel M.N.25-Aug-14 22:12 
AnswerRe: To Make it work just make two changes Pin
gazal20128-Nov-14 5:30
membergazal20128-Nov-14 5:30 
GeneralMy vote of 1 Pin
Georgettekh11-Apr-14 3:33
memberGeorgettekh11-Apr-14 3:33 
QuestionThe remote server returned an error: (401) Unauthorized. Pin
Gun Gun Febrianza15-Nov-13 8:14
member Gun Gun Febrianza15-Nov-13 8:14 
AnswerRe: The remote server returned an error: (401) Unauthorized. Pin
Pratik Thakker1-Apr-14 3:41
memberPratik Thakker1-Apr-14 3:41 
Questioncan we use same code for searching twitter statuses? Pin
webdev828-Nov-13 15:34
memberwebdev828-Nov-13 15:34 
QuestionGreat but Working only for one time Pin
Robot With Defects2-Oct-13 8:14
memberRobot With Defects2-Oct-13 8:14 
AnswerRe: Great but Working only for one time Pin
TheCodeKing16-Oct-13 3:29
memberTheCodeKing16-Oct-13 3:29 
Questionupdated version for api 1.1 Pin
THESIS_NAIM15-Jul-13 4:11
memberTHESIS_NAIM15-Jul-13 4:11 
QuestionThe remote server returned an error: (410) Gone. Pin
Biswanath Banik2-Jul-13 1:35
memberBiswanath Banik2-Jul-13 1:35 
AnswerRe: The remote server returned an error: (410) Gone. Pin
TheCodeKing16-Oct-13 3:28
memberTheCodeKing16-Oct-13 3:28 
Question401 error https://stream.twitter.com/1.1/statuses/filter.json Pin
Liza Kelly17-Jun-13 23:33
memberLiza Kelly17-Jun-13 23:33 
QuestionCRITICAL POINT TO REMEMBER Pin
Member 297027013-Jun-13 13:12
memberMember 297027013-Jun-13 13:12 
AnswerRe: CRITICAL POINT TO REMEMBER Pin
femi214-Jun-13 1:51
memberfemi214-Jun-13 1:51 
GeneralRe: CRITICAL POINT TO REMEMBER Pin
Liza Kelly14-Jun-13 2:52
memberLiza Kelly14-Jun-13 2:52 
GeneralRe: CRITICAL POINT TO REMEMBER Pin
Member 297027014-Jun-13 11:35
memberMember 297027014-Jun-13 11:35 
I should mention I'm also sending the request to the API as a "GET", not a "POST".

Here's my complete code (minus the keys). If you can't get this to pull info exactly as it is, then my guess would be an issue on the account side of things. I currently have my account permissions set to "read only", and I don't have a callback URL designated either. You may need to reset your account keys after any changes in settings you may do.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.Script.Serialization;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
namespace TwitterWidget
{
    public class Twidget : Control
    {
 

 

        public string Account { get; set; }
        public int Tweets { get; set; }
        public int CacheTTL { get; set; }
 

 

 
        static Dictionary<string, CachedData<List<string>>> Cache = new Dictionary<string, CachedData<List<string>>>();
        protected override void Render(HtmlTextWriter writer)
        {
            writer.Write("<ul style=\"list-style-type:none;margin-left:-35px\">");
 
            if (GetTweets().Count < 1)
            {
                writer.Write("<li style='padding-top:25px'>
 

 
<span style=\"color: Red ! important\">Twitter appears to be down. Please try again later.</span></li>");
            }
 
            else
            {
                foreach (var t in GetTweets().Take(Tweets))
                {
 
                    writer.Write("<li style='padding-top:25px'>{0}</li>", t);
 
                }
            }
 

            writer.Write("</ul>");
 

 
        }
 

 

        public List<string> GetTweets()
        {
            if ((!Cache.Keys.Contains(Account)) || ((DateTime.Now - Cache[Account].Time).TotalSeconds > CacheTTL))
                //new Thread(Update).Start(Account);  Putting this in a seperate thread appeared to rbe the reason first page load sometimes came up blank
                Update(Account);
            if (!Cache.Keys.Contains(Account))
                return new List<string>();
            return Cache[Account].Data;
        }
        public static void Update(object acc)
        {
            try
            {
                
                //OAuth stuff

                var oauth_consumer_key = "your key here";
                var oauth_consumer_secret = "your key secret here";
 
                var oauth_token = "your token here";
                var oauth_token_secret = "your token secret here";
 

                var oauth_version = "1.0";
                var oauth_signature_method = "HMAC-SHA1";
                var oauth_nonce = Convert.ToBase64String(
                                                  new System.Text.ASCIIEncoding().GetBytes(
                                                       DateTime.Now.Ticks.ToString()));
                var timeSpan = DateTime.UtcNow
                                                  - new DateTime(1970, 1, 1, 0, 0, 0, 0,
                                                       DateTimeKind.Utc);
                var oauth_timestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString();
                var slug = "libraries-and-news-sites";
                var owner_screen_name = "deskfeed";
                var resource_url = "https://api.twitter.com/1.1/lists/statuses.json";
 

 

 
                //these MUST be in alphabetical order!!!
                var baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}&owner_screen_name={7}&slug={6}";
 
                
                var baseString = string.Format(baseFormat,
                                            oauth_consumer_key,
                                            oauth_nonce,
                                            oauth_signature_method,
                                            oauth_timestamp,
                                            oauth_token,
                                            oauth_version,
                                            Uri.EscapeDataString(slug),
                                            Uri.EscapeDataString(owner_screen_name)
                                            );
 
                baseString = string.Concat("GET&", Uri.EscapeDataString(resource_url),
                             "&", Uri.EscapeDataString(baseString));
 

 
                var compositeKey = string.Concat(Uri.EscapeDataString(oauth_consumer_secret),
                                        "&", Uri.EscapeDataString(oauth_token_secret));
 
                string oauth_signature;
                using (System.Security.Cryptography.HMACSHA1 hasher = new System.Security.Cryptography.HMACSHA1(System.Text.ASCIIEncoding.ASCII.GetBytes(compositeKey)))
                {
                    oauth_signature = Convert.ToBase64String(
                        hasher.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(baseString)));
                }
 

 
                var headerFormat = "OAuth oauth_consumer_key=\"{0}\", oauth_nonce=\"{1}\", oauth_signature=\"{2}\", oauth_signature_method=\"{3}\", oauth_timestamp=\"{4}\", oauth_token=\"{5}\", oauth_version=\"{6}\"";
 
                var authHeader = string.Format(headerFormat,
                                        Uri.EscapeDataString(oauth_consumer_key),
                                        Uri.EscapeDataString(oauth_nonce),
                                        Uri.EscapeDataString(oauth_signature),
                                        Uri.EscapeDataString(oauth_signature_method),
                                        Uri.EscapeDataString(oauth_timestamp),
                                        Uri.EscapeDataString(oauth_token),
                                        Uri.EscapeDataString(oauth_version)
                                );
 

 

 
                ServicePointManager.Expect100Continue = false;
 

 
                // Actual Twitter request

                var getBody = "?slug=" + Uri.EscapeDataString(slug) + "&owner_screen_name=" + Uri.EscapeDataString(owner_screen_name);
                resource_url += getBody;
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(resource_url);
            
                request.Headers.Add("Authorization", authHeader);
            
                request.Method = "GET";
                request.ContentType = "application/x-www-form-urlencoded";
            
                WebResponse response = request.GetResponse();
                string responseData = new StreamReader(response.GetResponseStream()).ReadToEnd();
 
           
 

                string Account = (string)acc;
                var ls = new List<string>();
                var jss = new JavaScriptSerializer();
                var d = jss.Deserialize<List<Dictionary<string, dynamic>>>(responseData);
                
                foreach (var x in d)
 

                    ls.Add(("<a href='http://twitter.com/intent/user?screen_name=" + (string)x["user"]["screen_name"] + "'><img src='" + (string)x["user"]["profile_image_url"] + "' class='leftfloat' style='margin-left:0;margin-top:0;margin-bottom:0;margin-right:10px;clear:both;width:48px' alt='" + (string)x["user"]["name"] + "' />" + (string)x["user"]["name"] + ":</a> " + (Regex.Replace(((Regex.Replace((string)x["text"], @"[a-z]+://[^ ]+", y => "
<a href='" + y.Value.Replace("'", """) + "'>" + y.Value + "</a>", RegexOptions.Compiled | RegexOptions.IgnoreCase)).ToString()), @"#[^ ]+", z => "<a href='http://search.twitter.com/search?q=" + z.Value + "'>" + z.Value + "</a>", RegexOptions.Compiled | RegexOptions.IgnoreCase)) + "
<span style='font-size:9px;font-style:italic'>Tweeted " + (StringExtensions.ParseDateTime((string)x["created_at"])) + " ago</span>

GeneralRe: CRITICAL POINT TO REMEMBER Pin
Liza Kelly17-Jun-13 23:32
memberLiza Kelly17-Jun-13 23:32 
GeneralRe: CRITICAL POINT TO REMEMBER Pin
Liza Kelly18-Jun-13 5:02
memberLiza Kelly18-Jun-13 5:02 
GeneralRe: CRITICAL POINT TO REMEMBER Pin
Member 297027018-Jun-13 7:42
memberMember 297027018-Jun-13 7:42 
QuestionGetting 401 after modifying for Stream API Pin
femi213-Jun-13 2:12
memberfemi213-Jun-13 2:12 
AnswerRe: Getting 401 after modifying for Stream API Pin
Member 297027014-Jun-13 11:55
memberMember 297027014-Jun-13 11:55 
AnswerRe: Getting 401 after modifying for Stream API Pin
saravanan R16-Oct-13 0:55
membersaravanan R16-Oct-13 0:55 
QuestionFor Search Pin
Senthil Nathan15-Apr-13 13:59
memberSenthil Nathan15-Apr-13 13:59 
GeneralMy vote of 5 Pin
cellurl28-Mar-13 8:08
membercellurl28-Mar-13 8:08 
QuestionUsing Get method Pin
bobbykc12-Mar-13 5:59
memberbobbykc12-Mar-13 5:59 
AnswerRe: Using Get method Pin
bobbykc13-Mar-13 6:45
memberbobbykc13-Mar-13 6:45 
GeneralRe: Using Get method Pin
Roger Hamilton21-Mar-13 15:34
memberRoger Hamilton21-Mar-13 15:34 
GeneralRe: Using Get method Pin
saravanan R16-Oct-13 0:20
membersaravanan R16-Oct-13 0:20 
AnswerRe: Using Get method Pin
Treckstar14-Jun-13 5:51
memberTreckstar14-Jun-13 5:51 
GeneralGreat Article! Pin
girishraja11-Mar-13 13:10
membergirishraja11-Mar-13 13:10 
QuestionWorks perfectly with "POST" but when I try "GET" it keeps telling me 'UnAuthorized' "Twitter api 1.1" Pin
starrysky886-Nov-12 19:53
memberstarrysky886-Nov-12 19:53 
AnswerRe: Works perfectly with "POST" but when I try "GET" it keeps telling me 'UnAuthorized' "Twitter api 1.1" Pin
Travelthrprog4-Jan-13 9:56
memberTravelthrprog4-Jan-13 9:56 
AnswerRe: Works perfectly with "POST" but when I try "GET" it keeps telling me 'UnAuthorized' "Twitter api 1.1" Pin
bobbykc13-Mar-13 6:46
memberbobbykc13-Mar-13 6:46 
Questionerror : remote server returned an error: (401) Unauthorized. Pin
irafN7865-Nov-12 14:39
memberirafN7865-Nov-12 14:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150819.1 | Last Updated 26 Aug 2014
Article Copyright 2011 by TheCodeKing
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid