Auth をアプリケーションで使う

このページにある Auth コントローラの例を使うと、Simpleauth や Ormauth を使った基本的なユーザー認証をアプリケーションで利用できます。

前提

実際のアプリケーションを基にしたこの例では、Ormauth の実装に基づいて、ログイン、ログアウト、新規ユーザー登録、パスワードを忘れた場合の復旧措置について説明します。 「remember-me」(ログイン状態を保持)機能をサポートし、必要なフォームを生成するために フィールドセットを利用します。異なるセクションを説明しやすくするために、コントローラは別のパーツに分けられています。 使う時には、すべての例をコピーしてひとつのコントローラにまとめてかまいません。

また、すべてのデータ列で言語ファイル (それは使用された言語文字列すべてを含んいる login と呼ばれた言語ファイルを持っていると仮定) を利用しており、アプリケーションの動作を設定する application 構成ファイルやアプリケーションでのメッセージを扱う Message クラス、 それらは完全にこの例の範囲外です。アプリケーションのコントロールの動きとユーザーへのメッセージの伝達で あなたが利用するどのようなメカニズムにも置き換えることができます。

元のコードはテーマクラスを利用しますが、例は簡単にするために標準的なビューを利用するように合わせてあります。 Controller_Template を利用したい場合は、 View オブジェクトを返す代わりにテンプレートコンテンツを設定します。 テーマクラスを使用している場合は、必要な部分を代わりに設定します。

ログイン

これはログインメソッドのコード例です。入力フィールドに username (ユーザー名かメールアドレスのどちらか一方を含んでいてもよい)、 password そして、 remember (トグルとして使用されるのでチェックボックスであること) が含まれているフォームからの入力を想定しています。 まだフォームに入力が存在しない場合や、ログインの失敗が検出された場合、それは失敗しログインビューが表示されます。

public function action_login()
{
	// すでにログイン済み?
	if (\Auth::check())
	{
		// そうです、ユーザーが居た以前のページか、以前のページが
		// 検出できない場合はアプリケーションのダッシュボードへ戻ります
		\Messages::info(__('login.already-logged-in'));
		\Response::redirect_back('dashboard');
	}

	// ログインフォームが投稿された?
	if (\Input::method() == 'POST')
	{
		// 資格情報のチェック
		if (\Auth::instance()->login(\Input::param('username'), \Input::param('password')))
		{
			// ユーザーを覚えてほしい?
			if (\Input::param('remember', false))
			{
				// remember-me クッキーを作成
				\Auth::remember_me();
			}
			else
			{
				// 存在する場合、 remember-me クッキーを削除
				\Auth::dont_remember_me();
			}

			// ログイン、ユーザーが居た以前のページか、以前のページが
			// 検出できない場合はアプリケーションのダッシュボードへ戻ります
			\Response::redirect_back('dashboard');
		}
		else
		{
			// ログイン失敗、エラーメッセージを表示
			\Messages::error(__('login.failure'));
		}
	}

	// ログインページを表示
	return \View::forge('login/login');
}

ログアウト

これはログアウトメソッドのコード例です。 (次にページを要求した時に自動再ログインを引き起されるため) remember-me クッキーを削除します。

public function action_logout()
{
	// remember-me クッキーを削除し、意図的にログアウト
	\Auth::dont_remember_me();

	// ログアウト
	\Auth::logout();

	// ログアウトの成功をユーザーに知らせる
	\Messages::success(__('login.logged-out'));

	// そして、あなたがやってきたところに (もしくは、前のページを
	// 決定することができない場合はアプリケーションのホームページに) 戻る
	\Response::redirect_back();
}

登録

これは新規ユーザー登録メソッドのコード例です。この例は Ormauth を利用するので、登録フォームのフィールドセットを作成するために 認証ユーザーモデルが利用できるのは明らかです。あなたが Simpleauth を使用している場合、またフィールドセットのテンプレートとしてモデルを利用できますが、 我々はデータベースと対話するためにそれを利用していません。それを使用するということは Orm パッケージが読み込まれている必要があることを意味し、そうでなければモデルクラスは読み込まれません。

public function action_register()
{
	// 登録が有効?
	if ( ! \Config::get('application.user.registration', false))
	{
		// ユーザ登録ができないことをお知らせ
		\Messages::error(__('login.registation-not-enabled'));

		// そして、前のページ (またはホームページ) に戻る
		\Response::redirect_back();
	}

	// 登録用フィールドセットを作成
	$form = \Fieldset::forge('registerform');

	// CSRF 攻撃を防ぐために CSRF トークンを追加
	$form->form()->add_csrf();

	// そしてモデルのプロパティを持つフォームを投入
	$form->add_model('Model\\Auth_User');

	// そして、フルネームフィールド、それは、ユーザーのプロパティではなくプロファイルのプロパティ
	$form->add_after('fullname', __('login.form.fullname'), array(), array(), 'username')->add_rule('required');

	// パスワードの確認フィールドを追加
	$form->add_after('confirm', __('login.form.confirm'), array('type' => 'password'), array(), 'password')->add_rule('required');

	// パスワードは必要なので確認
	$form->field('password')->add_rule('required');

	// そして、新規ユーザーは彼らが属しているグループを選択できない (当たり前じゃないか)
	$form->disable('group_id');

	// それはフォーム上にはないので、それの不足で検証が失敗しないように
	$form->field('group_id')->delete_rule('required')->delete_rule('is_numeric');

	// 登録フォームが投稿された?
	if (\Input::method() == 'POST')
	{
		// 入力を検証
		$form->validation()->run();

		// 検証済みの場合、ユーザーを作成
		if ( ! $form->validation()->error())
		{
			try
			{
				// ユーザーを作成するために Auth を呼び出す
				$created = \Auth::create_user(
					$form->validated('username'),
					$form->validated('password'),
					$form->validated('email'),
					\Config::get('application.user.default_group', 1),
					array(
						'fullname' => $form->validated('fullname'),
					)
				);

				// ユーザーが正常に作成された場合
				if ($created)
				{
					// ユーザに通知
					\Messages::success(__('login.new-account-created'));

					// そして、前のページか、前のページがない場合は
					// アプリケーションのダッシュボードを表示
					\Response::redirect_back('dashboard');
				}
				else
				{
					// おっと、新しいユーザーの作成に失敗しました?
					\Messages::error(__('login.account-creation-failed'));
				}
			}

			// create_user() の呼び出し時の例外を捕捉
			catch (\SimpleUserUpdateException $e)
			{
				// メールアドレスが重複
				if ($e->getCode() == 2)
				{
					\Messages::error(__('login.email-already-exists'));
				}

				// ユーザー名が重複
				elseif ($e->getCode() == 3)
				{
					\Messages::error(__('login.username-already-exists'));
				}

				// これは起こり得ないが、ずっとそうとは限らない...
				else
				{
					\Messages::error($e->getMessage());
				}
			}
		}

		// 検証失敗、ポストされたデータをフォームへ再投入
		$form->repopulate();
	}

	// フォームにフィールドセットを渡し、新規ユーザの登録ビューを表示
	return \View::forge('login/registration')->set('form', $form, false);
}

このコードでは、新しいユーザーのアカウントはすぐさまアクティブになります。その段階でユーザーごとに別のグループを作成することで 検証段階を追加することや確認メールの送信をすることができます。そのメールには、新しい 'confirm' メソッドへのリンクがハッシュと 共に含まれている必要があり、そのハッシュの検証後、'validating' から 'active' へグループを変更します。これらのやり方は読者に委ねられています。 このハッシュを生成する方法や、どこに収納するか、どのようにメールを送信するか、 についていくつかの助言を得るために以下のパスワードの回復方法を見ることができます。

パスワードの復元

これはパスワード復元メソッドのコード例です。 これはユーザーが自分の電子メールアドレスを入力することができる 'email' 入力フィールドを持つフォームが必要です。これはユーザを検索し、復元用リンクを含むメールを送信するために使用されます。 アカウントおよびメールの推測を避けるために、メールが正しかったかどうかについてユーザーに目に見えるフィードバックはありません。

public function action_lostpassword($hash = null)
{
	// lostpassword フォームが投稿された?
	if (\Input::method() == 'POST')
	{
		// 投稿されたメールアドレスを持っている?
		if ($email = \Input::post('email'))
		{
			// このユーザーを知っている?
			if ($user = \Model\Auth_User::find_by_email($email))
			{
				// 復元ハッシュを生成
				$hash = \Auth::instance()->hash_password(\Str::random()).$user->id;

				// そして、ユーザープロファイルに格納
				\Auth::update_user(
					array(
						'lostpassword_hash' => $hash,
						'lostpassword_created' => time()
					),
					$user->username
				);

				// リセットリンクを記載したメールを送信
				\Package::load('email');
				$email = \Email::forge();

				// 電子メールメッセージを生成するためにビューファイルを使用
				$email->html_body(
					\Theme::instance()->view('login/lostpassword')
						->set('url', \Uri::create('login/lostpassword/' . base64_encode($hash) . '/'), false)
						->set('user', $user, false)
						->render()
				);

				// それに件名を与える
				$email->subject(__('login.password-recovery'));

				// 差出人と宛先を追加
				$from = \Config::get('application.email-addresses.from.website', 'website@example.org');
				$email->from($from['email'], $from['name']);
				$email->to($user->email, $user->fullname);

				// そして (すべてがうまくいけば) それを出す!
				try
				{
					// メールを送信
					$email->send();
				}

				// このようなことは起こりません。ユーザーのメールは検証済み、そうでしょう?
				catch(\EmailValidationFailedException $e)
				{
					\Messages::error(__('login.invalid-email-address'));
					\Response::redirect_back();
				}

				// 何かが間違っていた?
				catch(\Exception $e)
				{
					// エラーを管理者が確認できるログに記録
					logger(\Fuel::L_ERROR, '*** Error sending email ('.__FILE__.'#'.__LINE__.'): '.$e->getMessage());

					\Messages::error(__('login.error-sending-email'));
					\Response::redirect_back();
				}
			}
		}

		// フォームは投稿されたがメールアドレスは投稿した?
		else
		{
			// フォームに失敗を渡しユーザーに通知
			\Messages::error(__('login.error-missing-email'));
		}

		// 今メールしたこと (か、そうでないこと ;-)) をユーザーに通知
		\Messages::info(__('login.recovery-email-send'));
		\Response::redirect_back();
	}

	// フォームの投稿が無く、 URL で渡されたハッシュを持っていますか?
	elseif ($hash !== null)
	{

		// ハッシュをデコード
		$hash = base64_decode($hash);

		// get the userid from the hash
		$user = substr($hash, 44);

		// そして、この ID を持つユーザーを見つける
		if ($user = \Model\Auth_User::find_by_id($user))
		{
			// このユーザーは、このハッシュを持っていて、かつ、まだ失効していないか (24 時間未満の応答を許可) ?
			if (isset($user->lostpassword_hash) and $user->lostpassword_hash == $hash and time() - $user->lostpassword_created < 86400)
			{
				// ハッシュを無効に
				\Auth::update_user(
					array(
						'lostpassword_hash' => null,
						'lostpassword_created' => null
					),
					$user->username
				);

				// ユーザーをログインさせ、パスワードを変更させるためにプロフィールに行く
				if (\Auth::instance()->force_login($user->id))
				{
					\Messages::info(__('login.password-recovery-accepted'));
					\Response::redirect('profile');
				}
			}
		}

		// ハッシュがおかしい
		\Messages::error(__('login.recovery-hash-invalid'));
		\Response::redirect_back();
	}

	// フォームの投稿が無く、ハッシュも提供されていない。何をすべきか見当もつかない
	else
	{
		\Response::redirect_back();
	}
}

前述のように、この例では、 Ormauth を使用しているのでユーザーがテーブルにアクセスするために提供された ORM モデルを利用することができます。 Simpleauth を使用する場合は、このコードを代わりに使用します:

$user = \DB::select_array(\Config::get('simpleauth.table_columns', array('*')))
	->where('email', '=', $email)
	->from(\Config::get('simpleauth.table_name'))
	->as_object()->execute(\Config::get('simpleauth.db_connection'))->current();

	// このユーザーを知っている?
	if ($user)
	{
		// その他...

そして、 'id' のための 2 つ目の検索も同様です。 lostpassword フィールドのチェックもまた変更する必要があり、 Simpleauth では同様にそれらはプロファイルフィールドの配列に格納されます。これは、クエリ結果が profile_fields カラムを持つことを意味し、 それをアンシリアライズし、それが空でなければ、そこからハッシュとタイムスタンプを取得します。

ユーザ ID がハッシュに追加されていることにあなたは気が付くでしょう。これは Simpleauth をサポートするために行っています。 Ormauth を使用すると、メタデータの検索を行うことができ、メタデータから関連するユーザーオブジェクトを取得できます。 Simpleauth を使用すると、データは profile_fields カラムの内部にシリアル化された配列で格納されているため検索することができません。