http://fuelphp.jp/docs/1.7/packages/auth/opauth/intro.html

authに加えてormパッケージを有効にします。

	'always_load'  => array(
		'packages'  => array(
			'auth',
			'orm',
		),
	),

Authパッケージ用のテーブルをデータベースに反映しておきます。

$ oil refine migrate --packages=auth

Opauth を使うためには、まずComposerでライブラリーをインストールします。

今回はGoogleログインを試したいので、composer.jsonにopauth/opauthとopauth/googleを追加します。

"require": {
    "php": ">=5.3.3",
    "monolog/monolog": "1.5.*",
    "opauth/opauth": "0.4.*",
    "opauth/google": "dev-master",
    "fuelphp/upload": "2.0"
},
$ php composer.phar update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing opauth/opauth (0.4.4)
    Loading from cache

  - Installing opauth/google (dev-master 35df776)
    Cloning 35df77684c14acb346a8c3753ae3809852d1a47e

opauth/opauth suggests installing opauth/facebook (Allows Facebook authentication)
opauth/opauth suggests installing opauth/twitter (Allows Twitter authentication)
Writing lock file
Generating autoload files

次に、パッケージのopauth.phpfuel/app/configにコピーして、必要最小限の設定を追加します。

$ cp fuel/packages/auth/config/opauth.php fuel/app/config
return array(
	...
	'security_salt' => 'xxxxxxxxx',
	...
	'Strategy' => array(
		'Google' => array(
		  'client_id' => 'APP ID',
		  'client_secret' => 'APP_SECRET',
		  'scope' => 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.readonly',
		),
	 ),
);

Googleの場合は、Strategyにapp_idapp_secretではなく、client_idclient_secretのペアを指定します。
また、Google_Strategy.phpをみると、デフォルトのscopeは
https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
のみが指定されています。
Calendarのアクセスもログイン時に取得したいので、ここでは、
https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.readonly
も追加しています。

Googleカレンダーのリストを表示するコントローラーと認証用のコントローラーを作成します。

$ oil g controller calendar index
	Creating view: /Applications/MAMP/htdocs/fuel-gcal/fuel/app/views/template.php
	Creating view: /Applications/MAMP/htdocs/fuel-gcal/fuel/app/views/calendar/index.php
	Creating controller: /Applications/MAMP/htdocs/fuel-gcal/fuel/app/classes/controller/calendar.php

Controller_Calendarでは before でログイン済みかチェックして、未ログインなら user/login にリダイレクトします。

class Controller_Calendar extends Controller_Template
{
	public function before()
	{
		parent::before();
		if ( ! Auth::check())
		{
			Session::set_flash('error', 'You must be logged in.');
			Response::redirect('user/login');
		}
	}

	public function action_index()
	{
		$data["subnav"] = array('index'=> 'active' );
		$this->template->title = 'Calendar » Index';
		$this->template->content = View::forge('calendar/index', $data);
	}

}

認証用のコントローラも追加します。ビューが必要なloginアクションのみ指定します。

$ oil g controller user login
	Creating view: /Applications/MAMP/htdocs/fuel-gcal/fuel/app/views/user/login.php
	Creating controller: /Applications/MAMP/htdocs/fuel-gcal/fuel/app/classes/controller/user.php

ビューが必要ないoauth、callback、register、logoutアクションを追加すると、Controller_Userのインタフェースは以下のようになります。

class Controller_User extends Controller_Template
{
	public function action_login(){}

	public function action_oauth($provider = null){}

	public function action_callback(){}

	public function action_register(){}

	public function action_logout(){}
}

まずはログインページの表示

	public function action_login()
	{
		$this->template->title = 'User » Login';
		$this->template->content = View::forge('user/login');
	}
<p>
	<?php echo Html::anchor('user/oauth/google', 'Login in with Google', array('class'=>'btn btn-primary')); ?>
</p>

OAuth認証に関する各アクションは、公式ドキュメントの例: Using Opauth in your application を参考に埋めていきます。

	public function action_oauth($provider = null)
	{
		// bail out if we don't have an OAuth provider to call
		if ($provider === null)
		{
			\Messages::error(__('login-no-provider-specified'));
			\Response::redirect_back();
		}

		// load Opauth, it will load the provider strategy and redirect to the provider
		\Auth_Opauth::forge();
	}

action_oauthの$providerには、ログインページで指定したリンクをクリックしたときは”google”が指定されます。

<?php echo Html::anchor('auth/oauth/google', 'Login in with Google', array('class'=>'btn btn-primary')); ?>

次にcallbackです。

	public function action_callback()
	{
		try
		{
			$opauth = \Auth_Opauth::forge(false);

			$status = $opauth->login_or_register();

			$provider = $opauth->get('auth.provider', '?');

			switch ($status)
			{
				case 'linked':
				    \Session::set_flash('success', sprintf(__('login.provider-linked'), ucfirst($provider)));
				    $url = 'calendar';
				break;

				case 'logged_in':
				    \Session::set_flash('success', sprintf(__('login.logged_in_using_provider'), ucfirst($provider)));
				    $url = 'calendar';
				break;

				case 'register':
				    \Session::set_flash('success', sprintf(__('login.register-first'), ucfirst($provider)));
				    $url = 'user/register';
				break;

				case 'registered':
				    \Session::set_flash('success', __('login.auto-registered'));
				    $url = 'calendar';
				break;

				default:
				    throw new \FuelException('Auth_Opauth::login_or_register() has come up with a result that we dont know how to handle.');
			}

			\Response::redirect($url);
		}

		catch (\OpauthException $e)
		{
		    Log::error($e->getMessage());
		    \Response::redirect_back();
		}

		catch (\OpauthCancelException $e)
		{
		    exit('It looks like you canceled your authorisation.'.\Html::anchor('users/oath/'.$provider, 'Click here').' to try again.');
		}
	}

処理の流れについて説明します。

Googleの認可ページから戻ってきたときのOAuthのCallback処理は、Auth_Opauthのlogin_or_registerメソッドが処理してくれます。

次に、login_or_registerの戻り値である$statusによって、リダイレクト先を決めています。
ステータスは以下の通りです。

  • linked: ログイン済みの時に、現在ログインしているユーザにプロバイダーの情報が紐づけられた
  • logged_in: 紐付け済みのプロバイダーでログインした
  • registered: プロバイダーのログイン情報をもとに自動でユーザ登録した
  • register: プロバイダーのログイン情報を取得してユーザ登録していない。このときは、セッションにのみ情報を保持

プロバイダーがGoogleの場合は、auth.info.nicknameが取得されないので、新規ユーザで初回のCallback時のステータスは register になります。

次にregisterです。

公式ドキュメントのサンプルのregisterアクションは長々と書いてありますが、今回は、Google認証のみにして、UsernameまたはEメールとパスワードによる認証は省略するため、シンプルに書き直してみます。

ここで必要な処理は、action_callbackでセッションに入れられた情報を users とそれに紐付く user_providers テーブルに保存して、Auth::force_login を呼び出します。

	public function action_register()
	{
		if ($authentication = \Session::get('auth-strategy.authentication', array()))
		{
			try
			{
				$user = \Session::get('auth-strategy.user');

				$username_tmp = explode('@', $user['email']);
				$username = array_shift($username_tmp);
				// call Auth to create this user
				$user_id = \Auth::create_user(
					$username,
					'dummy',
					$user['email'],
					\Config::get('application.user.default_group', 1),
					array(
						'fullname' => $user['name'],
					)
				);

				// if a user was created succesfully
				if ($user_id)
				{
					// don't forget to pass false, we need an object instance, not a strategy call
					$opauth = \Auth_Opauth::forge(false);

					// call Opauth to link the provider login with the local user
					$insert_id = $opauth->link_provider(array(
						'parent_id' => $user_id,
						'provider' => $authentication['provider'],
						'uid' => $authentication['uid'],
						'access_token' => $authentication['access_token'],
						'secret' => $authentication['secret'],
						'refresh_token' => $authentication['refresh_token'],
						'expires' => $authentication['expires'],
						'created_at' => time(),
					));
					\Auth::instance()->force_login((int) $user_id);

					\Session::set_flash('success', __('login.new-account-created'));
					\Response::redirect_back('calendar');
				}

				else
				{
					\Session::set_flash('error', __('login.account-creation-failed'));
				}
			}

			// catch exceptions from the create_user() call
			catch (\SimpleUserUpdateException $e)
			{
				// duplicate email address
				if ($e->getCode() == 2)
				{
					\Session::set_flash('error', __('login.email-already-exists'));
				}

				// duplicate username
				elseif ($e->getCode() == 3)
				{
					\Session::set_flash('error', __('login.username-already-exists'));
				}

				// this can't happen, but you'll never know...
				else
				{
					\Session::set_flash('error', $e->getMessage());
				}
			}
		}

		else
		{
			\Session::set_flash('error', 'Failed to retrieve a user information from the provider.');
		}

		\Response::redirect_back('user/login');
	}

カレンダー表示の実装

gdataパッケージを追加します。

"require": {
    "php": ">=5.3.3",
    "monolog/monolog": "1.5.*",
    "opauth/opauth": "0.4.*",
    "opauth/google": "dev-master",
    "mp-php/fuel-packages-gdata": "dev-master",
    "fuelphp/upload": "2.0"
},

パッケージをインストールして、設定ファイルを fuel/app/config にコピーします。

$ php composer.phar update

gdataパッケージ用の設定を追加します。

$ cp fuel/packages/gdata/config/gdata.php fuel/app/config/
return array(
	'application_name' => '',
	'client_id' => 'APP ID',
	'client_secret' => 'APP_SECRET',
	'redirect_uri'     => '',
	'api_key'          => '',
	'scopes'           => array(),
	'access_type'      => 'offline',
);

opauthのGoogleStrategyの設定と重複しているのでなんとかしたいところです。

	public function action_index()
	{
		Package::load('gdata');
		$gdata = Gdata::forge(
			'Calendar',
			$instance_name = 'default',
			$config = array(
			)
		);
		
		$authentication = \Session::get('auth-strategy.authentication');
		
		$gdata->client->setAccessToken(json_encode(array(
			'access_token' => $authentication['access_token'],
			'expires_in' => $authentication['expires'],
			'refresh_token' => $authentication['refresh_token'],
			'created' => time()
		)));
		$cal = new Google_CalendarService($gdata->client);
		$data["calList"] = $cal->calendarList->listCalendarList();

		$this->template->title = 'Calendar &raquo; Index';
		$this->template->content = View::forge('calendar/index', $data);
	}
<ul>
	<?php foreach($calList['items'] as $calendar): ?>
		<li><?php echo $calendar['summary']; ?></li>
	<?php endforeach; ?>
</ul>

オフラインアクセスの処理

取得したアクセストークンは、期限(expires)が設定されているため定期的に、リフレッシュトークンを利用してアクセストークンを取得し直す必要があります。
Googleのドキュメント Offline access に手順が詳しく載っています。

作成中。。。