Jonathan Bennett

An Oauth Tale

So today I get to tell you a story. The instigator in this story is an evil wizard named Joe. His magical power is the dreaded curse called the “nerd snipe”. He hit the hero of the story, Jonathan, hard with his nerd snipe.

He proposed a “simple twitter tool” that would let him easily post queue’d tweets. There would be a few UX affordances to make this work well, but shouldn’t be anything especially problematic.

Enter the real antagonist. This is the true evil behind the evil wizard Joe. The one they call OAuth.

Turns out Oauth is a cruel master with many layers of hidden complexity and undocumented nuance.

Upon greeting his foe for the first time, our hero Jonathan pulled out his tried and true weapons. Omniauth should help vanquish this foe with a little help from omniauth-twitter2. Alas, no success. Round after round authentication errors tormented our hero. Should I be using API keys instead of client id/secret? Am I sending the right scopes? Is my callback URL permitted…

At last, a crack in the armor. Localhost is not allowed, switching to 127.0.0.1 and success, Twitter is finally showing the authorization page. We are off! And error… “Not authorized”… again…

“Confirm you have OAuth2 enabled in the developer portal”, check.
“Confirm you are sending the client id/secret base64 encoded”, check
“Confirm you are using the access token as a bearer token”, check\

So, using a hard coded—development environment only token—works, but using a generic, dynamically generated token does not.… I wonder what happens if I create a new production app with all the same settings.

It works is what it does.

Note to self, development app means only hard coded credentials. Thanks for the heads up on that.

With two major victories, Jonathan proceeds to finish off his foe. But it is not time yet. Twitter OAuth has one more trick up their sleeve. But that will have to wait till tomorrow.


The next day, our hero picks up his sword to finish off his task. Immediately he sees his tool has expired, and he reaches for his refresh token, with a sigh of relief knowing he asked for the offline.access scope. A quick refresh and … not authorized … 😩

Yes I am. You gave me a token, a refresh token AND I respected its expiration. Here we go again…

“Are you sending the refresh_token grant_type”, yes
“Are you sending the refresh token”, yes
“Are you sending the client id”, yes
“Are you sending the right content type”, yes
“Well you also need to send the authorization header”, but your docs don’t say that?!?!?\

Ok, "Authorization": "Bearer #{base64_encode(client_id:client_secret)}", “403: Not authorized”
Weird… "Authorization": "Bearer #{base64_encode(api_key:api_secret)}", “403: Not authorized”
Maybe "Authorization": "Bearer #{base64_encode(old_access_token)}", “403: Not authorized”

/me dives into google search…

“Try saying your server side only app is a insecure front end js and use no authorization” …nah, that can’t be it…

“Authorized” …🤨…😡…🤪

Our hero has defeated the foe OAuth, but the evil wizard Joe has escaped. This battle is won, but the war is incomplete