[Qiita] QiitaにおけるOAuth認証の流れ(PhoneGap/Cordova版)

  • 72
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Qiitaが用意しているAPIではv2からOAuth認証をサポートしています。
公式にもAPIのドキュメントがありますが、具体的な手順が分からない人のためにOAuth認証の実装の仕方をまとめてみました。
ここではPhoneGap/Cordovaを用いた時の実装を示しますが、他の環境・プログラミング言語で実装することを考えている人でも、実装の流れなどは参考になるかもしれません。

OAuth認証とは

OAuthってなんぞやって言う方は、既に以下のような記事があるので参照してみてください。

OAuth認証実装の流れ

最初にQiitaにおけるOAuth認証の流れを示します。

アプリケーションの登録

Qiitaの設定画面から「アプリケーション」-「登録中のアプリケーション」と進み、以下のフィールドを入力します。

  • アプリケーションの名前
  • アプリケーションの説明
  • WebサイトのURL
  • リダイレクト先のURL

この中で最も重要であるリダイレクト先のURLには、ユーザがOAuth認証でアプリを認可した時に自動的に移動するURLを指定します。
その他の入力項目はOAuth認証の実装にそこまで影響を与えないので、適宜入力していきます。

入力した内容を保存すると、 Client IDClient Secret が得られるのでこれらをメモしておきます。

OAuth認可画面を開く

OAuth認可画面のURLへ移動します。
OAuth認可画面のURLや指定するパラメータを以下に示します。

パラメータ 説明
URL https://qiita.com/api/v2/oauth/authorize
client_id 「アプリケーションで取得」した Client ID
scope アプリからQiitaに対して許可する操作
複数指定する場合は値の間に+を入れる
state ユーザがアプリを認可した時の移動先URLにパラメータとして設定される16進数の値
認可されたことを確認するために使用する

scopeの具体的な値には以下のようなものがあります。
ここで例えば、Qiitaへの読み込み書き込みを許可する認可画面を表示したい場合は、scope=read_qiita+write_qiitaとします。

説明
read_qiita Qiitaからの読み込みを許可します
write_qiita Qiitaへの書き込みを許可します
read_qiita_team Qiita:Teamからの読み込みを許可します
write_qiita_team Qiita:Teamへの書き込みを許可します

client_idmy_client_idscoperead_qiitastateFEDCBA9876543210であった場合の認可画面のURLは以下のようになります。
https://qiita.com/api/v2/oauth/authorize?client_id=my_client_id&scope=read_qiita&state=FEDCBA9876543210

アプリの認可を検知

ユーザがアプリを認可すると、「アプリケーションの登録」で設定したリダイレクトURLに移動します。
またリダイレクトURLのパラメータには以下の値が設定されます。

パラメータ 説明
state 「OAuth認可画面」でパラメータとして設定したstateの値
code アクセストークンを取得するために必要な値

stateの値を「OAuth認可画面」でパラメータとして設定したstateの値と比較することで、認可が正しく行われたことを確認することができます。

アクセストークンの取得

アクセストークンを取得します。
アクセストークンの取得はアクセストークンを取得するためのURLに対して以下のパラメータをつけてPOST送信するだけです。

パラメータ 説明
URL https://qiita.com/api/v2/access_tokens
client_id 「アプリケーションの登録」で取得した Client ID
client_secret 「アプリケーションの登録」で取得した Client Secret
code 「アプリの認可の検知」で取得した code

POST送信して認証に成功した場合、以下のパラメータが返されます。

パラメータ 説明
access_token アクセストークン

Qiita APIを利用する

「アクセストークンの取得」で得たアクセストークンを使うことで、認証が必要なQiita APIを利用することが可能になります。
Qiita APIの場合、HTTPリクエストヘッダにアクセストークンを含める必要がある点に注意が必要です。

OAuth認証の実装(PhoneGap/Cordova)

OAuth認証の実装の仕方をPhoneGap/Cordovaを例にして説明します。

認可処理

PhoneGapでは認可画面を開くために、InAppBrowserプラグインを利用します。
InAppBrowserプラグインがインストールされていない場合はインストールしましょう。

 $ cordova plugin add org.apache.cordova.inappbrowser`

最初に、OAuth認可画面のURLを開きます。

cb = window.open(url, "_blank", 'location=no,toolbar=no,clearcache=yes,clearsessioncache=yes');

認可画面を開くだけならこれだけでも良いのですが、このままだとInAppBrowserを閉じることができません。
このためページが読み込まれるたびにURLを確認して、アプリが認可されたことを確認したらブラウザを閉じる処理を追加します。
具体的には、Qiitaで設定したリダイレクトURLと一致し、かつパラメータ state の値が認証画面を開くときに指定した state と一致していることで確認します。

cb.addEventListener('loadstart', function(e) {
    var loc = e.url;    // 読み込みを開始したURL
    // リダイレクトURLへ飛んだことを確認する
    if (loc.indexOf(redirectURL) != -1) {
        // パラメータ取得
        var params = getQueryParams(loc);
        // stateの整合性を確認
        if (params['state'] != state) {
            alert("not match state parameter.");
            return;
        }
        cb.close();
    }
});

アクセストークンの取得

取得したパラメータをアクセストークンを取得ためのURLに対して、パラメータを設定してPOST送信します。
POST送信が成功した場合、アクセストークンが返ってきます。
返ってきたアクセストークンを用いることで、認証を必要とするQiita APIを利用することができるようになります。

$.ajax({
    url: 'https://qiita.com/api/v2/access_tokens',
    type: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    dataType: 'json',
    data: JSON.stringify(params),
    success: function(res) {
        // アクセストークン取得成功。
        // res['token']にアクセストークンが保存されている。
    },
    error: function(e) {
        alert("Failed to get access token");  // 認証失敗
    }
});

APIの利用

アクセストークンを取得できたので、Qiitaが提供するAPIを使ってみましょう。
ここではアプリを認可したユーザ名を取得する例を示しています。
HTTPリクエストヘッダに取得したアクセストークンを指定することで、アプリを認可したユーザ名を取得することができます。

$.ajax({
    url: 'https://qiita.com/api/v2/authenticated_user',
    type: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + authToken,   // アクセストークン
    },
    dataType: 'json',
    success: function(res) {
        // 取得成功
        // res['id']にアプリを認可したユーザが保存されている。
    },
    error: function(e) {
        alert("Failed to get authenticated user ID");   // 取得失敗
    }
});

OAuth認証のサンプル

以上の実装の流れからOAuth認証のサンプルを示します。
サンプルのソースコード内にコメントを書いているので、詳細な説明は省きます。
なお、以下の文字列は適宜自分が利用するものに置き換えてください。

文字列 説明
APP_CLIENT_ID 「アプリケーションの登録」で取得した Client ID
APP_CLIENT_SECRET 「アプリケーションの登録」で取得した Client Secret
APP_PERMISSION_SCOPE 「OAuth認可画面」で設定する scopeの値
APP_REDIRECT_URL 「アプリケーションの登録」で設定した リダイレクト先のURL
APP_REDIRECT_STATE 「アプリケーションの登録」で取得した State
oauth.js
var QiitaOAuth = (function() {
    var obj = function(){};
    obj.prototype = {
        init: function() {
            // 認証向けのパラメータ
            this.authURL = 'https://qiita.com/api/v2/oauth/authorize';   // 認証用URL
            this.clientID = APP_CLIENT_ID;                               // Client ID
            this.clientSecret = APP_CLIENT_SECRET;                       // Client Secret
            this.redirectURL = APP_REDIRECT_URL;             // 認可時のリダイレクト先のURL
            this.scope = APP_PERMISSION_SCOPE;                       // アクセス範囲
            this.state = APP_REDIRECT_STATE;                             // 認可時に正当性を確認するための値

            this.cb = undefined;
        },
        login: function(successCallback) {
            // 認可画面を開く
            var url = this.getAuthURL();
            this.cb = window.open(url, "_blank", 'location=no,toolbar=no,clearcache=yes,clearsessioncache=yes');
            this.loginSuccess = successCallback;    // 認可が完了した時に呼ばれる関数
            this.redirectParams = undefined;        // アクセストークンを取得するために必要なパラメータ

            var self = this;
            // アプリを認可したイベントを捕捉するため、ページ読み込み開始のイベントを捕捉するように設定
            self.cb.addEventListener('loadstart', function(e) {
                var loc = e.url;    // 読み込みを開始したURL
                // リダイレクトURLへ飛んだことを確認する
                if (loc.indexOf(self.redirectURL) != -1) {
                    // パラメータ取得
                    var params = getQueryParams(loc);
                    // stateの整合性を確認
                    if (params['state'] != self.state) {
                        alert("not match state parameter.");
                        return;
                    }
                    self.cb.close();
                    self.redirectParams = {
                        'client_id': self.clientID,
                        'client_secret': self.clientSecret,
                        'code': params['code']
                    };
                    self.loginSuccess(self.redirectParams);
                }
            });
        },
        // 認証用URLの作成
        getAuthURL: function() {
            var url;
            url = this.authURL + "?" + $.param({
                client_id: this.clientID,
                scope: this.scope,
                state: this.state
            });
            return url;
        }
    };
    return obj;
})();


// URLからパラメータを取得する関数
function getQueryParams(url)
{
    var params = {};
    var queryStart = url.indexOf('?');

    // パラメータが存在しない場合
    if (queryStart == -1) { return params; }

    var query = url.substring(queryStart + 1);
    var p = query.split('&');

    // パラメータを取得
    for (var i = 0; i < p.length; ++i) {
        var elm = p[i].split('=');
        var key = decodeURIComponent(elm[0]);
        var value = decodeURIComponent(elm[1]);
        params[key] = value;
    }

    return params;
}

// アプリが認可されたときに呼ばれる、アクセストークンを取得する関数
function onOAuthSuccess(params)
{
    $.ajax({
        url: 'https://qiita.com/api/v2/access_tokens',
        type: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        dataType: 'json',
        data: JSON.stringify(params),
        success: function(res) {
            // 取得成功
            getAuthUser(res['token']);
        },
        error: function(e) {
            // 取得失敗
            alert("Failed to get access token");
        }
    });
}

// アプリを認可したユーザを取得する関数
function getAuthUser(authToken)
{
    $.ajax({
        url: 'https://qiita.com/api/v2/authenticated_user',
        type: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + authToken,   // 取得したアクセストークンを指定
        },
        dataType: 'json',
        success: function(res) {
            // 取得成功
            authUser = res['id'];
        },
        error: function(e) {
            // 取得失敗
            alert("Failed to get authenticated user ID");
        }
    });
}

var qoa = new QiitaOAuth();
qoa.init();
qoa.login(onOAuthSuccess);

おわりに

実装が面倒そうに思えるOAuth認証ですが、手続きの流れがわかってしまえば実装は簡単です。
今後OAuth認証を実装される方にとってこの記事が参考になれば幸いです。

参考情報