OAuth in GNOME Shell Extensions

As you might know, I am the maintainer of the GNOME Twitchlive shell extensions. The Twitchlive panel allows you to see whether your favourite Twitch streamers are online or not.

A few months ago, Twitch started to require OAuth authentication for its endpoints and things started to go sideways. Now there are two ways to communicate with Twitch's API:

  1. Authenticate your requests with a pre-shared application secret.
  2. Obtain a client secret through user authentication and use this secret.

The first variant is only possible for server-to-server applications, as you're not allowed to distribute the application secret to users. So the second variant it is and we need to authenticate the user.

In the OAuth process you open a webpage (of the oauth provider) with a callback url. That webpage contains a login form and if you enter valid credentials, the webpage will redirect you to your given callback url, passing as additional information the authorization token you can use to make a valid API call.

To receive the token you need a webserver that you can redirect to. Even though GNOME's supposed to have a generic OAuth implementation it uses for its online services but it's not generic at all and you can't hook into that. You also can't create a webserver otherwise from within the GNOME Shell. Until a few weeks ago:

I've implemented a small mechanism by starting a python-based webserver through the extension that has just enough capabilities to receive the OAuth token and write it to a file. As far as I know, that's a world first. If you base OAuth of you extension on this work, give me a shout on twitter.

And now to the code which I think I boiled down right to the essentials (you'll also find it on github):

We need to open a browser with the callback within the extension:

function trigger_oauth() {
  const url = "https://id.twitch.tv/oauth2/authorize?response_type=token&client_id=" + client_id + "&redirect_uri=http://localhost:8877&scope=";
  GLib.spawn_command_line_async("xdg-open " + url);
  GLib.spawn_sync(null, ["python3", oauth_receiver,  oauth_token_path], null, GLib.SpawnFlags.SEARCH_PATH, null);
}

And the python3 reciver code:

from http.server import *;
from urllib.parse import *;
import sys;
import os.path;

page = """<html>
<head><title>Twitchlive GNOME Shell extension OAuth</title></head>
<body><script>var tokens=document.location.hash.substring(1);
document.write("<a href=\\"/tokens?" + tokens + "\\"> To finish OAuth-Process click here</a>");
</script></body>
"""

class handler(BaseHTTPRequestHandler):
  def log_requests(self):
      pass

  def do_GET(self):
      print(self.path)
      if self.path == '/':
          # initial call from twitch
          self.send_response(200)
          self.send_header("Content-Type", "text/html")
          self.end_headers()

          self.wfile.write(page.encode())
      elif self.path.startswith('/tokens'):
          # our own call
          code = parse_qs(urlparse(self.path).query)['access_token'][0]
          open(sys.argv[1], 'w').write(code)

          self.send_response(200)
          self.send_header("Content-Type", "text/plain")
          self.end_headers()

          self.wfile.write(b"Thank You. You can close this page.")
          sys.exit(0)

Note that the OAuth-Token is passed as the url fragment that doesn't show up in the actual query so you really need a browser to read that value.

And for now ... it works.


Comments and Discussion is provided by Disqus. They are tracking site and user interaction. Please refer to their privacy policy for information about data usage and retention. If you still want to look at comments or comment yourself, enable disqus by clicking here.

Previous: Podcast Time Machine

links

social

Theme based on notmyidea