認証
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()
メソッドが呼び出され、その後、ログアウトが成功したことを確認するわかりやすいメッセージが表示されます。
まとめ
ログインとユーザーログアウトのためのリンクがあります。検証には組み込みの認証システムを使用し、ログイン情報は設定ファイルにあります。これは簡単なテストアプリケーションであるためです。また、編集フォームも保護したので、ログインしているユーザーのみが投稿を追加および編集できます。