CodeIgniterにOAuth2のサービスプロバイダーとクライアントを実装する。

CodeIgniterにOAuth2のサービスプロバイダーとクライアントを実装する。

コードはこちらで公開しています。(データベースの設定、oauth2-server-phpのインストールと修正が必要)

凡例

ドメイン
example.jp
ドキュメントルートパス
$HTDOC_DIR
サービスプロバイダー用ライブラリ
bshaffer/oauth2-server-php · GitHub
クライアント用ライブラリ(CodeIgniter Sparks)
philsturgeon/codeigniter-oauth2 · GitHub
サービスプロバイダー側
http://example.jp/oauth2/
http://example.jp/api/
クライアント側
http://example.jp/client/
  • OAuthの情報を保存するためにMySQLを使用する
  • サービスプロバイダー側はデモなのでHTTPでアクセスする。(本来はHTTPSを使用するべき)

前準備

  1. ドキュメントルート下にCodeIgniterをインストール済み(http://example.jp/でWelcomeページが表示)
  2. .htacess設置を設置してindex.phpをURLから除去する
    CodeIgniter の URL : CodeIgniter ユーザガイド 日本語版
  3. application/config/config.phpで以下を変更
    // index.phpを取り除く
    $config['index_page'] = '';
    
    //デフォルトのセッションを使う
    $config['encryption_key'] = '適当な値';
  4. application/config/autoload.phpで以下を変更
    $autoload['libraries'] = array('session');
  5. サービスプロバイダライブラリ(oauth2-server-php )インストール
    cd $HTDOC_DIR
    cd application/third_party
    git clone https://github.com/bshaffer/oauth2-server-php.git
  6. CodeIgniter Sparksインストール
    cd $HTDOC_DIR
    php -r "$(curl -fsSL http://getsparks.org/go-sparks)"
  7. クライアントライブラリ(codeigniter-oauth2 )インストール
    php tools/spark install oauth2
  8. DB設定
    データベースを作成し、ユーザ権限を与え、application/config/database.phpを設定しておく。
  9. テーブル作成&初期データ追加
    -- テーブル作成(oauth2-server-phpのテストを流用)
    CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT);
    CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires DATETIME, scope TEXT);
    CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires DATETIME, scope TEXT);
    
    -- クライアントアプリケーションを登録
    INSERT INTO oauth_clients (client_id, client_secret) VALUES ("demoapp", "demopass");

作成したファイル

サービスプロバイダ側OAuth2コントローラー

application/controllers/oauth2.php

<?php
require_once APPPATH . 'third_party/oauth2-server-php/src/OAuth2/Autoloader.php';

class Oauth2 extends CI_Controller
{
    private $_server;
    private $_request;

    public function __construct()
    {
        parent::__construct();

        OAuth2_Autoloader::register();

        include(APPPATH.'config/database'.EXT);
        $db_config = $db['default'];
        $connection = array(
            'dsn' => sprintf(
                '%s:dbname=%s;host=%s',
                $db_config['dbdriver'],
                $db_config['database'],
                $db_config['hostname']
            ),
            'username' => $db_config['username'],
            'password' => $db_config['password']
        );
        $storage = new OAuth2_Storage_Pdo($connection);
        $this->_server = new OAuth2_Server($storage);
        $this->_server->addGrantType(new OAuth2_GrantType_UserCredentials($storage));
        $this->_request = OAuth2_Request::createFromGlobals();
    }

    public function authorize()
    {
        switch ($this->input->server('REQUEST_METHOD')) {
        case 'GET':
            // リクエストチェック
            $params = $this->_server->validateAuthorizeRequest($this->_request);

            if (!$params) {
                $this->_server->getResponse();
            } else {
                $this->load->view('oauth2', $params);
            }
            break;
        case 'POST':
            /*
             * ここで認証処理
             */
            // 認証処理の結果、ユーザIDが決定
            $userid = 1;

            // 認可応答
            $authorized = (bool) $this->input->post('authorize');
            $response = $this->_server->handleAuthorizeRequest($this->_request, $authorized, $userid);
            $location = $response->getHttpHeader('Location');
            header("Location: $location");
            break;
        }
    }

    public function access_token()
    {
        $this->_server->handleGrantRequest($this->_request)->send();
    }
}

サービスプロバイダ側APIコントローラー

application/controllers/api.php

<?php
require_once APPPATH . 'third_party/oauth2-server-php/src/OAuth2/Autoloader.php';

class Api extends CI_Controller
{
    private $_server;
    private $_request;

    public function __construct()
    {
        parent::__construct();

        OAuth2_Autoloader::register();

        include(APPPATH.'config/database'.EXT);
        $db_config = $db['default'];
        $connection = array(
            'dsn' => sprintf(
                '%s:dbname=%s;host=%s',
                $db_config['dbdriver'],
                $db_config['database'],
                $db_config['hostname']
            ),
            'username' => $db_config['username'],
            'password' => $db_config['password']
        );
        $storage = new OAuth2_Storage_Pdo($connection);
        $this->_server = new OAuth2_Server($storage);
        $this->_server->addGrantType(new OAuth2_GrantType_UserCredentials($storage));
        $this->_request = OAuth2_Request::createFromGlobals();
    }

    public function friends()
    {
        if (!$this->_server->verifyAccessRequest($this->_request)) {
            $this->_server->getResponse()->send();
        } else {
            $response = new OAuth2_Response(array('friends' => array('friend1', 'friend2', 'friend3')));
            $response->send();
        }
    }
}

クライアント側コントローラー

application/controllers/client.php

<?php

class Client extends CI_Controller
{
    private $_provider;

    public function __construct()
    {
        parent::__construct();

        $this->load->helper('url_helper');
        $this->load->spark('oauth2/0.4.0');

        $this->_provider = $this->oauth2->provider('example', array(
            'id' => 'demoapp',
            'secret' => 'demopass',
            'redirect_uri' => site_url().'client/'
        ));
    }

    public function index()
    {
        $data = array(
            'auth_url' => null,
            'access_token' => null
        );

        if (!$this->input->get('code')) {
            $data['auth_url'] = $this->_provider->authorize();
        } else {
            try {
                // アクセストークンの取得
                $token = $this->_provider->access($this->input->get('code'));

                // アクセストークン
                $access_token = $token->access_token;
                $data['access_token'] = $access_token;

                /*
                 *以後使えるように保存しておく
                 */

                // 保存していおいたアクセストークンを使ってAPIアクセス
                $token = OAuth2_Token::factory('access', array('access_token' => $access_token));
                $friends = $this->_provider->get_friends($token);
                $data['friends'] = $friends;
            } catch (OAuth2_Exception $e) {
                show_error($e->getMessage());
            } catch (Exception $e) {
                show_error($e->getMessage());
            }
        }

        $this->load->view('client', $data);
    }
}

サービスプロバイダの認可ページビュー

application/views/oauth2.php

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>サービスプロバイダー</title>

<!-- Bootstrap -->
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
    padding-top: 60px;
}
</style>
<link href="/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
</head>
<body>
<div>
  <div>
    <div> <a href="#">サービスプロバイダー側</a> </div>
  </div>
</div>
<div>
  <div>
    <p>この連携アプリを認証すると、次の動作が<strong>許可されます</strong>。</p>
    <ul>
      <li>XXXXXXXXXXXXXXXXXXXXXXXX</li>
      <li>YYYYYYYYYYYYYYYYYYYYYYYY</li>
    </ul>
  </div>
  <fieldset>
    <legend>Login(※デモなので入力の必要なし)</legend>
    <div>
      <label for="username">ユーザ名</label>
      <div>
        <input name="username" type="text" />
      </div>
    </div>
    <div>
      <label for="password">パスワード</label>
      <div>
        <input name="password" type="password" />
      </div>
    </div>
  </fieldset>
  <div>
    <form action="/oauth2/authorize?client_id=<?php echo $client_id ?>&redirect_uri=<?php echo $redirect_uri ?>&response_type=<?php echo $response_type ?>" method="post">
      <input type="hidden" name="authorize" value="1" />
      <button type="submit">許可する</button>
      <button type="button" onclick="document.getElementById('cancel').submit()">Cancel</button>
    </form>
    <form id="cancel" action="/oauth2/authorize?client_id=<?php echo $client_id ?>&redirect_uri=<?php echo $redirect_uri ?>&response_type=<?php echo $response_type ?>" method="post">
      <input type="hidden" name="authorize" value="0" />
    </form>
  </div>
  <div>
    <p>この連携アプリを認証しても、次の動作は<strong>許可されません</strong>。</p>
    <ul>
      <li>ZZZZZZZZZZZZZZZZZZZZZZZZ</li>
    </ul>
  </div>
</div>
<script src="http://code.jquery.com/jquery-latest.js"></script> 
<script src="/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>

クライアントページビュー

application/views/client.php

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>クライアント</title>
<!-- Bootstrap -->
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<style>
body {
    padding-top: 60px;
}
</style>
<link href="/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" />
</head>
<body>
<div>
  <div>
    <div> <a href="/">クライアント側</a> </div>
  </div>
</div>
<div>
  <?php if ($auth_url) { ?>
  <p><a href="<?php echo $auth_url ?>">認証・認可ページへ</a></p>
  <?php } else { ?>
  <p>アクセストークン:<?php echo $access_token ?></p>
  <p>APIからのレスポンス<br />
    <?php var_dump($friends) ?>
  </p>
  <?php } ?>
</div>
<script src="http://code.jquery.com/jquery-latest.js"></script> 
<script src="/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>

codeigniter-oauth2内で使用されるサービスプロバイダーを定義したExampleプロバイダー

sparks/oauth2/0.4.0/libraries/Provider/Example.php

<?php
/**
 * Example OAuth2 Provider
 *
 * @package    CodeIgniter/OAuth2
 * @category   Provider
 */

class OAuth2_Provider_Example extends OAuth2_Provider
{
    protected $method = 'POST';

    public function url_authorize()
    {
        return site_url() . 'oauth2/authorize';
    }

    public function url_access_token()
    {
        return site_url() . 'oauth2/access_token';
    }

    public function get_friends(OAuth2_Token_Access $token)
    {
        $url = site_url() . 'api/friends?' . http_build_query(array('access_token' => $token->access_token));
        $response = file_get_contents($url);
        $data = json_decode($response);

        return $data;
    }
}

oauth2-server-phpの一部修正

codeigniter-oauth2がアクセストークンリクエストを行う際、デフォルトはGETメソッドでレスポンスはapplication/x-www-form-urlencodedで返される前提で作られている。またPOSTメソッドの場合はJSONで返される前提になっている。

一方oauth2-server-phpのアクセストークンレスポンスはJSONで返される。

そこで、今回はアクセストークンリクエストをPOSTで行うようになっている。

しかし、そのままだとアクセストークンリクエストを受け付けるoauth2-server-php側でリクエストの際送られるパラメータcode(認可コード)が見つからないというエラーになる。

これは、application/third_party/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php内で、codeを取得するのが、URLのクエリストリングから取得するようになっているためである。
一方 OAtuhの仕様書(参考資料の1.)ではアクセストークンリクエストのパラメータはHTTPリクエストボディで与えるようになっているため、application/third_party/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.phpを編集してHTTPリクエストボディからcodeを取得するように変更する。

application/third_party/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php

23c23
< if (!isset($request->query['code']) || !$request->query['code']) {
---
> if (!isset($request->request['code']) || !$request->request['code']) {
33c33
< if (!$tokenData = $this->storage->getAuthorizationCode($request->query['code'])) {
---
> if (!$tokenData = $this->storage->getAuthorizationCode($request->request['code'])) {
43c43
< if (!$request->query('redirect_uri') || urldecode($request->query('redirect_uri')) != $tokenData['redirect_uri']) {
---
> if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $tokenData['redirect_uri']) {

デモ

http://example.jp/clientにアクセス

OAuthクライアント Top

サービスプロバイダー側の認可エンドポイントに遷移し、認証・認可する

サービスプロバイダー 認証・認可ページ

クライアント側に戻り、アクセストークンを取得し、さらにそれを使ってAPIにアクセスして結果を表示する。

アクセストークン取得&APIアクセス

参考資料

  1. draft-ietf-oauth-v2-31 – The OAuth 2.0 Authorization Framework
  2. OAuth 2.0でWebサービスの利用方法はどう変わるか – @IT

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中