GitHub Pages 上で Web Application Flow で GitHub API を使用する

# tl;dr

[OAuth.io](https://oauth.io/) を利用すれば自前で Web サーバを用意することなしに Web Application Flow で GitHub API を利用することができる。

[デモ](https://yuya-takeyama.github.io/ghapp-on-ghpages/)

# GitHub Pages で GitHub API を使ってみよう

JavaScript でちょっとアレコレするだけの Web アプリを公開するのにサーバを用意するのは面倒です。
Heroku も料金プランの改定により 24 時間稼働させるには有料となってしまいました。

そこで [GitHub Pages](https://pages.github.com/) です。
GitHub Pages は無料で静的な Web ページをホストしてくれます。

# GitHub Pages で GitHub API を利用する際の問題点

GitHub API を利用するには OAuth のアクセストークンが必要です。
アクセストークンを取得する方法は [Web Application Flow と Non-Web Application Flow](https://developer.github.com/v3/oauth/) が用意されていますが、いずれも問題があります。

## Web Application Flow を利用する場合

Web Application Flow というのはいわゆる GitHub ログインによるフローです。
一旦 GitHub 側のアクセストークンの付与を許可する画面に遷移します。

ただし、このアクセストークンを取得する過程で、Client Secret と呼ばれる秘密鍵が必要になります。
Web サーバを伴うアプリケーションであればサーバ内に保持しておけば良いのですが、GitHub Pages を使う場合それも露出することになってしまいます。

## Non-Web Application Flow を利用する場合

Non-Web Application というのは BASIC 認証によりアクセストークンを取得する方法です。
GitHub のログイン ID とパスワードによりアクセストークンが取得できるので、アクセストークンさえ保持しておけば、ログイン ID とパスワードは保持しておかなくてよい、というものです。

ただ、この場合ユーザから見るとそのアプリが信頼できるかどうかが問題になります。
ログイン ID・パスワードを保持しない、とアプリ側が主張していても、本当に保持していないかどうかは簡単にはわかりません。

また、GitHub 上で既にログインしているユーザに対してもログイン ID・パスワードの入力が求められてしまう、という点で面倒です。

## Personal Access Token について

GitHub の API へのアクセスは OAuth 以外にも [Personal Access Token](https://github.com/settings/tokens) による方法もあります。
これは GitHub にログインして設定画面上で自分でアクセストークンを生成・取得するというものです。
ユーザ ID・パスワードをアプリ提供者に渡す必要がなく、いざとなればアクセストークンを無効化させればよい、という点でセキュリティ上のリスクは Web Application Flow と同等 (つまり Non-Web Application Flow よりは安全) と考えてよいでしょう。

ただし、これは Web Application Flow と違ってシームレスなフローではないので、単純に面倒です。

# OAuth.io とは

そこで役に立つのが OAuth.io という Web サービス (PaaS? BaaS?) です。
OAuth.io は OAuth の利用を簡単にするためのサービスとその SDK をセットで提供しています。

OAuth.io に GitHub API の Client ID と Client Secret を預けておくことで、本来 Web Application Flow で実装すべき処理を肩代わりしてもらうことができます。

## OAuth.io 

まずは[登録](https://oauth.io/)が必要です。
Facebook や Twitter 等のアカウントを持っていれば簡単に登録できます。

## アプリケーションの作成

ログインした時点で Default Application が作成されているので、初回は不要です。

アプリケーションを作成すると Public Key と呼ばれるキーも同時に作成されます。

この時点で Domains & URLs whitelist としてコールバック URL を指定することもできます。
ここでは以下のように設定してみました。

`https://yuya-takeyama.github.io/ghapp-on-ghpages/#callback`

複数指定できるので、ローカル開発用に localhost の URL を登録しておいても良いでしょう。
ここではドメイン名だけで指定することもできますが、オープンリダイレクト脆弱性のリスクを考えると URL で指定した方が良いでしょう。

## GitHub 上での OAuth Application の登録

これは OAuth.io を利用しない通常の手順同様です。

[Register new application](https://github.com/settings/applications/new)

ただし、Authorization callback URL には必ず以下を指定する必要があります。

`https://oauth.io/auth`

最終的には `https://yuya-takeyama.github.io/ghapp-on-ghpages/#callback` に戻されますが、事前に OAuth.io 側でアクセストークンの取得等を行った上で行われるようになっています。

## OAuth.io に GitHub の Client ID と Client Secret を登録する

OAuth.io は数百の OAuth Provider に対応しており、その設定はダッシュボードの Integrated APIs から行います。
Add APIs ボタンを押して GitHub を選択しましょう。
(OAuth Provider の一覧が出るまで少し時間がかかります)

すると Client ID と Client Secret を入力するフォームが現れるので、GitHub 上で OAuth Application を作成したときに生成された値をそれぞれ入力します。

scope には取得したい権限を必要に応じて入力します。
空にした場合は公開情報のみ Read-only で取得できます。
(詳細は[ヘルプページ](https://developer.github.com/v3/oauth/#scopes)を参照)

# 実装例

というわけで簡単なものを作ってみました。
GitHub ログインを行うと、ログインしたユーザのログイン ID とアバターを表示するだけです。

[デモアプリ](https://yuya-takeyama.github.io/ghapp-on-ghpages/)

## ソースコード

OAuth.io の JavaScript SDK として [oauth-js](https://github.com/oauth-io/oauth-js) というのが配布されているので、それを使います。
`OAuth.initialize()` に OAuth.io から発行された Public Key を渡すと、`OAuth.redirect()` でログイン画面にリダイレクトされます。
`OAuth.popup()` というポップアップウィンドウによる方法も用意されていますが、ユーザにポップアップを許可させる必要があるので今回は使いませんでした。

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>GitHub App on GitHub Pages using Web Application Flow</title>
</head>
<body>
  <h1>GitHub App on GitHub Pages using Web Application Flow</h1>
  <input type="button" id="login" value="GitHub Login">
  <div id="success"></div>
  <script type="text/javascript" src="./bower_components/jquery/dist/jquery.min.js"></script>
  <script type="text/javascript" src="./bower_components/oauth-js/dist/oauth.min.js"></script>
  <script>
  $(function() {
    $('#login').click(function() {
      OAuth.initialize('DeFu7MqHWRudsPhHXehuNF2cwxE');
      OAuth.redirect('github', 'https://yuya-takeyama.github.io/ghapp-on-ghpages/#callback');
    });
    if (location.hash === '#callback') {
      location.hash = '';
      OAuth.callback('github')
        .done(function(res) {
          res.get('/user')
            .done(function(res) {
              var result = $('<div>')
                .append($('<div>').text('Hello, ' + res.login + '!'))
                .append($('<img>').attr('src', res.avatar_url));
              $('#success').html(result);
            })
            .fail(function(res) {
              window.alert('Failed to get usre info: see console for details');
              console.dir(err);
            })
        })
        .fail(function(err) {
          window.alert('Failed to authorization: see console for details');
          console.dir(err);
        });
    }
  });
  </script>
</body>
</html>
```

## SDK 使用上の注意点

### oauth-js は Bower でインストールする。

npm にも同名のライブラリが登録されていますが、ややこしいことに全くの別物です！
SDK の取得には必ず Bower を使ってください。

```
$ bower install oauth-js
```

### Browserify は使わずにグローバル変数で参照する

イマドキの JavaScript 開発では Browserify を使う人が多いことでしょう。
[debowerify](https://github.com/eugeneware/debowerify) というプラグインを使うことで Bower でインストールしたライブラリも `browserify` できるようですが、oauth-js についてはうまく `require` することができませんでした。
詳しくはわかりませんでしたが、普通に `<script>` タグで読み込んでグローバル変数 `OAuth` を使いましょう。