認証

Netteは、私たちのサイトで認証をプログラムする方法を提供しますが、何も強制しません。実装は私たち次第です。NetteにはNette\Security\Authenticatorインターフェースが含まれており、これはauthenticateという1つのメソッドのみを要求します。このメソッドは、私たちが望む任意の方法でユーザーを検証します。

ユーザーを認証する方法はたくさんあります。最も一般的な認証方法はパスワードによるものです(ユーザーは名前またはメールアドレスとパスワードを提供します)が、他の方法もあります。一部のサイトで「Facebookでログイン」ボタンやGoogle/Twitter/GitHubによるログインを見たことがあるかもしれません。Netteを使用すると、任意のログイン方法を持つことができ、それらを組み合わせることもできます。それは私たち次第です。

通常は独自の認証システムを作成しますが、このシンプルな小さなブログでは、設定ファイルに保存されているパスワードとユーザー名に基づいてログインする組み込みの認証システムを使用します。これはテスト目的に適しています。したがって、設定ファイルconfig/common.neonに次のsecurityセクションを追加します。

security:
	users:
		admin: secret  # ユーザー 'admin', パスワード 'secret'

NetteはDIコンテナに自動的にサービスを作成します。

ログインフォーム

これで認証の準備が整い、ログイン用のユーザーインターフェースを準備する必要があります。SignPresenterという名前の新しいPresenterを作成しましょう。これは次のことを行います。

  • ログインフォーム(ログイン名とパスワード付き)を表示する
  • フォーム送信後、ユーザーを認証する
  • ログアウトオプションを提供する

ログインフォームから始めましょう。Presenterでフォームがどのように機能するかはすでに知っています。したがって、Presenter SignPresenterを作成し、メソッドcreateComponentSignInFormを記述します。次のようになります。

<?php
namespace App\Presentation\Sign;

use Nette;
use Nette\Application\UI\Form;

final class SignPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentSignInForm(): Form
	{
		$form = new Form;
		$form->addText('username', 'ユーザー名:')
			->setRequired('ユーザー名を入力してください。');

		$form->addPassword('password', 'パスワード:')
			->setRequired('パスワードを入力してください。');

		$form->addSubmit('send', 'ログイン');

		$form->onSuccess[] = $this->signInFormSucceeded(...);
		return $form;
	}
}

ユーザー名とパスワードのフィールドがあります。

テンプレート

フォームはテンプレートin.latteでレンダリングされます。

{block content}
<h1 n:block=title>ログイン</h1>

{control signInForm}

ログインコールバック

次に、フォームが正常に送信された直後に呼び出されるユーザーログイン用のコールバックを追加します。

コールバックは、ユーザーが入力したユーザー名とパスワードを受け取り、それらを認証システムに渡すだけです。ログイン後、ホームページにリダイレクトします。

private function signInFormSucceeded(Form $form, \stdClass $data): void
{
	try {
		$this->getUser()->login($data->username, $data->password);
		$this->redirect('Home:');

	} catch (Nette\Security\AuthenticationException $e) {
		$form->addError('ユーザー名またはパスワードが正しくありません。');
	}
}

メソッドUser::login()は、ユーザー名とパスワードが設定ファイルの情報と一致しない場合に例外をスローします。すでに知っているように、これにより赤いエラーページが表示されるか、本番モードではサーバーエラーを通知するメッセージが表示される可能性があります。しかし、それは望ましくありません。したがって、この例外をキャッチし、フォームにわかりやすい、ユーザーフレンドリーなエラーメッセージを渡します。

フォームでエラーが発生すると、フォームのあるページが再描画され、フォームの上に、ユーザーが間違ったログイン名またはパスワードを入力したことを通知するわかりやすいメッセージが表示されます。

Presenterの保護

投稿を追加および編集するためのフォームを保護します。これはPresenter EditPresenterで定義されています。目標は、ログインしていないユーザーがページにアクセスできないようにすることです。

Presenterのライフサイクルの開始直後に実行されるstartup()メソッドを作成します。これにより、ログインしていないユーザーがログインフォームにリダイレクトされます。

public function startup(): void
{
	parent::startup();

	if (!$this->getUser()->isLoggedIn()) {
		$this->redirect('Sign:in');
	}
}

リンクの非表示

認証されていないユーザーは、createページやeditページを表示できなくなりましたが、それらへのリンクはまだ表示されます。これらも非表示にする必要があります。そのようなリンクの1つはテンプレートapp/Presentation/Home/default.latteにあり、ログインしているユーザーのみが表示できるようにする必要があります。

n:属性 n:ifを使用して非表示にできます。この条件がfalseの場合、コンテンツを含む<a>タグ全体が非表示のままになります。

<a n:href="Edit:create" n:if="$user->isLoggedIn()">投稿を作成</a>

これは、次の記述の省略形です(tag-ifと混同しないでください):

{if $user->isLoggedIn()}<a n:href="Edit:create">投稿を作成</a>{/if}

同様の方法で、テンプレートapp/Presentation/Post/show.latteのリンクも非表示にします。

ログインリンク

実際にログインページにアクセスするにはどうすればよいでしょうか?そこにつながるリンクはありません。それでは、テンプレート@layout.latteに追加しましょう。適切な場所を見つけてみてください – ほとんどどこでもかまいません。

...
<ul class="navig">
	<li><a n:href="Home:">記事</a></li>
	{if $user->isLoggedIn()}
		<li><a n:href="Sign:out">ログアウト</a></li>
	{else}
		<li><a n:href="Sign:in">ログイン</a></li>
	{/if}
</ul>
...

ユーザーがログインしていない場合は、「ログイン」リンクが表示されます。それ以外の場合は、「ログアウト」リンクが表示されます。このアクションもSignPresenterに追加します。

ログアウト後すぐにユーザーをリダイレクトするため、テンプレートは必要ありません。ログアウトは次のようになります。

public function actionOut(): void
{
	$this->getUser()->logout();
	$this->flashMessage('ログアウトしました。');
	$this->redirect('Home:');
}

logout()メソッドが呼び出され、その後、ログアウトが成功したことを確認するわかりやすいメッセージが表示されます。

まとめ

ログインとユーザーログアウトのためのリンクがあります。検証には組み込みの認証システムを使用し、ログイン情報は設定ファイルにあります。これは簡単なテストアプリケーションであるためです。また、編集フォームも保護したので、ログインしているユーザーのみが投稿を追加および編集できます。

ここでは、ユーザーログイン権限の検証について詳しく読むことができます。