OAuth Issue / log in works (sometimes)

Mar 18, 2011 at 5:32 PM

Hi everyone,

 

I just implemented linqtotwitter to my ASP.NET project.

I registered my application via dev.twitter.com

I got a issue that appears sometimes when I'm trying to login from twitter, I got the "Error 401 not authorize" from auth.BeginAuthorization(Request.Url);

It happens about 1 time on a total of 2 attempts.

 

Am I missing something ? Is twitter api busy ?

 

Thanks in advance

Coordinator
Mar 18, 2011 at 8:18 PM

Twitter does get busy sometimes and starts sending 503 and 401 error messages randomly.  Another reason you might be getting 401 is if the authorization sequence is done incorrectly.  i.e. trying to complete before beginning, so you might want to double check your code or do some logging to check the order of operations.  You can also open Fiddler to check this by looking at the HTTP requests when your log-in works and comparing it to when you get the 401's.  If all is identical, then the problem could be at Twitter.  Nevertheless, you should code defensively and assume that you could receive an error from Twitter at any time.

Joe

Mar 19, 2011 at 1:47 PM

Thanks for your reply Joe.

I guess the problem come from twitter that sends 401 error randomly.

I check my code and I try with the demos (asp web form & mvc) from the linqtotwitter package, it do the same.

Coordinator
Mar 21, 2011 at 3:37 AM

This might be an issue with Twitter because it seems like others are having the same problem.  I've begun participating in this thread: http://groups.google.com/group/twitter-development-talk/browse_thread/thread/1011865585b129b5/4c77e1df54df23f0#4c77e1df54df23f0.

I'll continue to follow up and share what I learn.

Joe

Coordinator
Mar 22, 2011 at 12:39 AM

So, it looks like Twitter has an OAuth timing issue.  Many other Twitter libraries, besides LINQ to Twitter, are having the same problem, which you can see via the link to the thread in my previous post.

I do have a work-around that seems to be helping on my projects.  I put a 10.5 second delay before calling CompleteAuthorization (getting an access token).  Here's an example:

    Thread.Sleep(10500)
    completed = auth.CompleteAuthorization(url)

You might need to adjust the delay for your own purposes.

Joe

Mar 23, 2011 at 7:49 AM
Edited Mar 23, 2011 at 8:53 AM

Ok, thanks for all Joe.

I'll test the sleep...

 

Edit: I just noticed that it's working better with I.E, auth works all the time :O

Coordinator
Mar 23, 2011 at 3:15 PM

Interesting; Different results on different browsers might be related to the timing issue.  If you're using Silverlight, that might make a difference on timing in the browser too.

Joe

Apr 1, 2011 at 1:48 AM

Happening every time now.  Feel like I have to scrap this to work with Twitter sign-in.

I checked the twitter api dev thread you pointed to above, and saw some blog posts at the bottom.

I tried both solutions, adding Registry Keys and the <generatePublisherEvidence enabled="false"/> to machine.config.

Still, getting the error.  Intermittently it will work if I'm using the debug server for VS, but it seems to always fail if I go directly through IIS 7.5 (Win 7).

Is this a Twitter problem or a .NET problem?

Coordinator
Apr 1, 2011 at 2:29 AM

Your problem looks different. Do you have a small example of controller code that reproduces the problem?

Joe

Apr 1, 2011 at 6:18 AM

Joe, I'm sorry but it seems I may have ran into a limit on how many times I could log in with the same account.

I'm starting to test with several different accounts so if I run into it again, I'll let you know.

Thanks for the prompt response.

Coordinator
Apr 1, 2011 at 2:57 PM

No problem.  If there's a bug, I'll welcome hearing about it.  I vaguely remember a similar situation in the past where I received an error and tried to re-process the request with the same callback parameters that Twitter sent to me, which didn't work because of a corrupt query string.  Sometimes those are hard to reproduce unless you're actually encountering the error condition consistently.

Good luck,

Joe

Apr 2, 2011 at 2:08 AM

FYI - this is NOT related to the Issue I created about the extra ? in the Querystring.

That's a real issue I think you should look at.

This had to do with always getting a 401 back from Twitter after authentication within the auth.CompleteAuthorization(Request.Url) call.

Unfortunately, I'd like to handle the error gracefully, but it seems the WebException.Response doesn't make the HttpStatusCode public.

Do you know how to get that out without parsing the response string or WebException message?

Apr 7, 2011 at 4:21 AM

Nope, it's back.  I'm following the issue on the google groups and twitter dev pages.

Not sure if it's something you can solve, but the timeout strategy is terrible from a user perspective.  Anyway, it's all we seem to have until it's resolved, or is it?

Coordinator
Apr 7, 2011 at 2:59 PM

If there was anything more I could do, I would definitely do it.  I did put the 15 second delay in the PinAuthorizer, which is available via a source code download.  However, I'm not going to deploy the code with the 15 second delay as a release because it's only a temporary work-around.  The other work around, switching https to http is supported via the LINQ to Twitter API, without changing code.  Check out my blog post, Modifying Service URLs with LINQ to Twitter.

I've contributed to the following issue too:

Issue 2118: Converting a request_token to an access_token results in a 401

If anyone reading this post hasn't done so yet, please star the issue at the above link to help increase the amount of attention being placed on the problem.

Joe

Apr 7, 2011 at 6:17 PM

Joe,

Thanks for the quick update, and it seems like there is an active following of Issue 2118 for the Twitter API.

I checked out your blog post, which details how to update the URL for the TwitterContext, but this doesn't help me as I get the 401 during OAuth login.

After looking at it, should I be updating the OAuthXXXUrl properties of the MvcAuthorizer?
i.e. changing this code:

auth = new MvcAuthorizer
{
	Credentials = credentials,
};

auth.CompleteAuthorization(Request.Url);

to this code:

auth = new MvcAuthorizer
{
	Credentials = credentials,
	OAuthRequestTokenUrl = "http://api.twitter.com/oauth/request_token",
	OAuthAuthorizeUrl = "http://api.twitter.com/oauth/authorize",
	OAuthAccessTokenUrl = "http://api.twitter.com/oauth/access_token"
};

auth.CompleteAuthorization(Request.Url);

Does this look right?

Thanks again,

Jeff

Apr 7, 2011 at 6:30 PM

Joe,

Sorry for the spam, and I appreciate all the help.

It appears if I do what I did above, using your MVC Example Demo, my friends tweets come back as empty.
I even tried to change the URL for the TwitterContext to use http://api.twitter.com/ for both URLs in the constructor thinking it might need to coincide with the protocol used to get the tokens, and it didn't change anything.

I did report on the Issue 2118 that changing the URLs to use HTTP, Twitter redirects to HTTPS for the "oauth/authorize" call before redirecting back to my app.
Is this part of your code or is this a Twitter thing?

Thanks a lot,

Jeff

Apr 7, 2011 at 6:46 PM

Wow, I'm an idiot.  Forget about the empty tweet stream, I forgot that I started using an account that's not following anyone.

As for the amount of posts, its an annoying issue and I'm just hoping I get this out there for someone else to find as well because I hate not being able to find the answer to using something that is supposed to save me time.

Still wondering about the HTTPS redirect when I set the URLs to use HTTP above.

Coordinator
Apr 7, 2011 at 6:53 PM

On the OAuth URLs, that would be correct.  It's the reason why I made the properties public because through versioning and other situations, like this, you need to be able to change them and make them configurable in your own system.  Let me know if there's problems.

Joe

Coordinator
Apr 7, 2011 at 7:27 PM

On the redirect, I was looking at the MvcAuthorizer and see that it's not as flexible as the other authorizers in letting you specify the callback.  The callback is what Twitter uses to redirect back to your app.  I'll need to refactor that to give you the ability to specify the callback.  That said, you do have the ability to change the callback parameter on CompleteAuthoriization, which is Request.Url in the demo code.  It would look something like:

string httpUrl = Request.Url.ToString().Replace("https", "http");

auth.CompleteAuthorization(httpUrl);

I'll take a look at MvcAuthorizer BeginRequest later.  If you want to mess with it before I get to it, let me know.  Here's the code that needs changed:

You can create your own MvcAuthorizer, like this:

    public class MyMvcAuthorizer : WebAuthorizer
    {
        public ActionResult BeginAuthorization()
        {
            return new MyMvcOAuthActionResult(this);
        }
    }

All I've done is renamed it and I've renamed the ActionResult, MyMvcActionResult, modified here:

    public class MvcOAuthActionResult : ActionResult
    {
        private WebAuthorizer m_webAuth;

        public MvcOAuthActionResult(WebAuthorizer webAuth)
        {
            m_webAuth = webAuth;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            m_webAuth.PerformRedirect = authUrl =>
            {
                HttpContext.Current.Response.Redirect(authUrl);
            };

            m_webAuth.BeginAuthorization(m_webAuth.Callback);
        }
    }

Notice how I changed the argument to BeginAuthorization from Request.Url to to the base WebAuthorizer.Callback property, which would be used like this:

            auth = new MyMvcAuthorizer
            {
                Credentials = credentials,
                Callback = httpUrl
            };

Using the new authorizer and setting the callback to a non-https URL as done previously for CompleteAuthorization.

It all has to be tested and coded and everything that comes with it before I can put it in LINQ to Twitter, but it might be a good temporary work-around until I have it in-place.

Joe

 

 

 

 

Apr 8, 2011 at 4:53 AM

Thanks again, Joe.  This is good stuff.

I received a notification from the Issue 2118 thread and it appears its been fixed.

It works for me now, so we'll see.

Coordinator
Apr 20, 2011 at 12:23 AM

Getting the HttpStatusCode - you have to convert the returned types to their actual types, like this:

(int)((ex as System.Net.WebException).Response as System.Net.HttpWebResponse).StatusCode

which will result in the value: 400.  Without the int cast, it will return the enum value "BadRequest".

Joe 

 

Apr 20, 2011 at 7:45 PM

For status code I used the following try catch block

try
{
	//Thread.Sleep(15000);
	auth.CompleteAuthorization(Request.Url);
}
catch (WebException webEx)
{
	HttpWebResponse response = webEx.Response as HttpWebResponse;
	if (response != null)
	{
		string msg = "";
		switch (response.StatusCode)
		{
			case HttpStatusCode.Unauthorized:
				msg = "Twitter sent an Unauthorized response";
				break;
			case HttpStatusCode.ServiceUnavailable:
				msg = "Twitter service Unavailable";
				break;
			case HttpStatusCode.RequestTimeout:
				msg = "Twitter request timed out";
				break;
			case HttpStatusCode.Forbidden:
				msg = "Twitter responded that the request was to a forbidden URL";
				break;
			case HttpStatusCode.Gone:
				msg = "Twitter responded that the requested URL is no longer available";
				break;
			case HttpStatusCode.NotFound:
				msg = "Twitter responded that the requested URL was not found";
				break;
			case HttpStatusCode.InternalServerError:
				msg = "Twitter reported an Internal Server Error";
				break;
		}
		return RedirectToAction("ServiceError", new { id = id, serviceId = twitterSvc.Id, msg = msg });
	}
}

If you care about them, you can trap more HttpStatusCodes.