今回は、Laravel5(5.6)にてメール認証を行った会員登録の実装を紹介します。
実務でLaravelのサイトを作ったときメール認証の実装方法が日本語で解説されていないと感じたので。
手順は良いからソース知りたいって人→ココ
実装機能
- 仮会員登録
- メール認証
- 本会員登録
流れ
- 仮会員としてメールアドレスを登録
- 本会員登録用URLを添付したメール送信
- URLクリックで入力フォームへ(メール認証)
- 本会員情報の入力
- 本会員登録完了
画面の流れ
今回は、以下の画面構成で考えました。
- 仮会員登録画面
- 確認画面
- 完了画面
- (仮会員登録認証メール)
- 本会員登録画面
- 確認画面
- 完了画面
実装
さて、実装に入りましょう。
仮会員登録
まだ、make:auth
を実行していない方は先に実行してください。
php artisan make:auth
実行済みの方は、次に進みます。
Usersテーブルにtokenを追加する
仮会員登録で発行するURLtokenのフィールドを、Users
テーブルに追加します。
Artisanコマンドでmigrationファイルを作成しましょう。
php artisan make:migration add_columns_users_table
補足:
--table
オプションでテーブル名を指定してもいいです。
php artisan make:migration add_votes_to_users_table --table=users
作成したmigrationファイルに2カラム追加します。
- email_verified : 認証済みかどうか
- email_token : email用トークン
スキーマの内容は以下のようにします。
class AddColumnsUsersTable extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->tinyInteger('email_verified')->default(0);
$table->string('email_verify_token')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('email_verified');
$table->dropColumn('email_verify_token');
});
}
}
重要:
down()
に記述を忘れずに入力してください。migrate:rollback
コマンドを実行したとき、down()の処理を行います。up()に対する逆の処理がdown()に正しく記載されていない場合、migrate:rollback
とmigrate:reset
が失敗してしまいます。
migrationファイルの作成が終わったら、忘れずにmigrate
コマンドでmigrationの適用をしてください。
追記: tinyIntegerはbooleanでもいいです。そちらのほうが自然かも
php artisan migrate
.envファイルの変更
.envファイルにemail設定を追加します。
ここでは、mailtrapを使用して実装しています。
アカウントを作るだけなので簡単です。詳しくはこちらMailtrapでLaravelの簡単メール送信テストなどを参考にしてください。
.envファイルは以下のように修正します。
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=fec77971a86b66
MAIL_PASSWORD=9c480963d8d7df
MAIL_ENCRYPTION = tls
+ MAIL_FROM_ADDRESS=from@example.com
+ MAIL_FROM_NAME=LaravelExampler
- MAIL_FROM_ADDRESS
- MAIL_FROM_NAME
上記を追加することで、メール送信処理のアドレスと送信者名のデフォルト値を設定することができます。
メール作成
認証メールに使用するviewと、tokenを返すクラスを作成します。
php artisan make:mail EmailVerification
このコマンドで、app/Mail
にMailableを継承したEmailVerificationクラスが作成されます。
このクラスにメール送信に使用するviewやメールタイトルなどの設定を記述することになります。
EmailVerificationの設定
このクラスには2つのメソッドがあります。
- constructor()
- build()
コンストラクタはご存知の通りです。buildは実行時に呼ばれる関数です。
コンストラクタに、Users情報を渡しましょう。
クラス変数を追加します。
protected $user;
constructor()で引数に$userを設定し、クラス変数にセットします。
public function __construct($user)
{
$this->user = $user;
}
補足:こうすることで、build()にて
$this->user->email_verify_token
と参照することができます。(ユーザーごとのtokenを判別できます)
build()を更新します。
public function build()
{
return $this
->subject('【site】仮登録が完了しました')
->view('auth.email.pre_register')
->with(['token' => $this->user->email_verify_token,]);
}
}
これで、viewと一緒にtokenを渡すことができました!
Emailテンプレートの作成
さて、今度はEmailの本文となるviewを作りましょう。
今回はviews/auth/email
ディレクトリにpre_register.blade.php
を作成しました。(イケてるファイル名に差し替えて使ってください。)
サイトへのアカウント仮登録が完了しました。<br>
<br>
以下のURLからログインして、本登録を完了させてください。<br>
{{url('register/verify/'.$token)}}
Userモデルの更新
Userに追加したカラムにデータをセットするには、一手間いります。
Laravelでは、モデルへのカラムへの挿入時には$fillable
もしくは$guarded
にカラムを設定する必要があります。
詳しくはこちらなどを参照ください:
【Laravel:Eloquentクラス】fillableとguardedの指定はどちらかだけでいい
それではUser.php
へ追加したカラムを$fillable
に設定しましょう。
protected $fillable = [
'name', 'email', 'password',
+ 'email_verified', 'email_verify_token',
];
仮会員登録フォームの追加
仮会員登録時に「メールアドレス」「パスワード」の登録を行う仕様にします。
make:authで作成したものは「名前」が入っているので取り除きましょう。
register.blade.php
から名前の項目を削除します。
- <div class="form-group row">
- <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
-
- <div class="col-md-6">
- <input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>
-
- @if ($errors->has('name'))
- <span class="invalid-feedback">
- <strong>{{ $errors->first('name') }}</strong>
- </span>
- @endif
- </div>
- </div>
register.blade.php
のformの送信先を変更します。
- <form method="POST" action="{{ route('register') }}">
+ <form method="POST" action="{{ route('register.pre_check') }}">
仮会員確認
次に、仮登録確認画面のviewを追加しましょう。
views/auth
にregister_check.blade.php
を追加します。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">仮会員登録確認</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">メールアドレス</label>
<div class="col-md-6">
<span class="">{{$email}}</span>
<input type="hidden" name="email" value="{{$email}}">
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">パスワード</label>
<div class="col-md-6">
<span class="">{{$password_mask}}</span>
<input type="hidden" name="password" value="{{$password}}">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
仮登録
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
web.php
(route)に追加します。
Route::post('register/pre_check', 'Auth\RegisterController@pre_check')->name('register.pre_check');
RegisterControllerの更新
「名前」を削除したので、Controllerに反映しましょう。
Validator()
から'name'
の行を削除してください。
protected function validator(array $data)
{
return Validator::make($data, [
- 'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
削除しないままだと、存在しない項目のバリデーションが実行されることになります。
次に、確認画面のビューに遷移できるように修正しましょう。
web.phpに記載したメソッド:pre_check()
を追加し、returnにviewを返します。
ここでは、受け取ったリクエストのバリデーションチェックを行い、ビューに値を渡します。
passwordは、表示するときにマスキングするようにしています。
public function pre_check(Request $request){
$this->validator($request->all())->validate();
//flash data
$request->flashOnly( 'email');
$bridge_request = $request->all();
// password マスキング
$bridge_request['password_mask'] = '******';
return view('auth.register_check')->with($bridge_request);
}
これで、仮会員登録画面〜確認画面までが実装できました。
仮登録完了画面の追加
views/auth
にregistered.blade.php
を追加してください。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">仮会員登録完了</div>
<div class="card-body">
<p>この度は、ご登録いただき、誠にありがとうございます。</p>
<p>
ご本人様確認のため、ご登録いただいたメールアドレスに、<br>
本登録のご案内のメールが届きます。
</p>
<p>
そちらに記載されているURLにアクセスし、<br>
アカウントの本登録を完了させてください。
</p>
</div>
</div>
</div>
</div>
</div>
@endsection
RegisterControllerのcreate()
にemail_verify_token
を追加します。
ここで、作成したEmailVerification
を使用し、Mail::
を使用してメール送信処理を実行します。
protected function create(array $data)
{
- return User::create([
- 'email' => $data['email'],
- 'password' => Hash::make($data['password']),
- ]);
+ $user = User::create([
+ 'email' => $data['email'],
+ 'password' => Hash::make($data['password']),
+ 'email_verify_token' => base64_encode($data['email']),
+ ]);
+ $email = new EmailVerification($user);
+ Mail::to($user->email)->send($email);
+ return $user;
}
/register
で呼ばれるメソッドregister()
に上記のcreate()
メソッドを紐づけます。
public function register(Request $request)
{
event(new Registered($user = $this->create( $request->all() )));
return view('auth.registered');
}
補足:
/register
は、make:authコマンドでweb.php
に作成される、Auth::routes()
に含まれています。
確認したい場合は、php artisan route:list
コマンドを実行してみてください。
今のままだと、UsersテーブルのnameがNot nullになっているのでエラーになってしまいます。
name
にnullable
を設定しましょう。
php artisan make:migration change_column_users_table --table=users
ここまでで、仮会員登録が実装できました。
うまくできないときは、こちらのソースと比較して見てください
本会員登録フォームの追加
本会員登録用URLがクリックされると本会員登録フォームに遷移されるようにしましょう。
ルーティング(web.php)に以下を追加します。
Route::get('register/verify/{token}', 'Auth\RegisterController@showForm');
RegisterControllerにshowForm()
を追加しましょう。
エラーチェックをした後、Viewを返す処理をしています。
public function showForm($email_token)
{
// 使用可能なトークンか
if ( !User::where('email_verify_token',$email_token)->exists() )
{
return view('auth.main.register')->with('message', '無効なトークンです。');
} else {
$user = User::where('email_verify_token', $email_token)->first();
// 本登録済みユーザーか
if ($user->status == config('const.USER_STATUS.REGISTER')) //REGISTER=1
{
logger("status". $user->status );
return view('auth.main.register')->with('message', 'すでに本登録されています。ログインして利用してください。');
}
// ユーザーステータス更新
$user->status = config('const.USER_STATUS.MAIL_AUTHED');
$user->verify_at = Carbon::now();
if($user->save()) {
return view('auth.main.register', compact('email_token'));
} else{
return view('auth.main.register')->with('message', 'メール認証に失敗しました。再度、メールからリンクをクリックしてください。');
}
}
}
以下のときをエラーをとしています。
- 登録済みのトークンのとき
- メール認証済みのユーザーのとき
- 本登録済みのユーザーのとき
エラーのときは、Viewにメッセージを通知するようにしています。
statusカラム追加
メール認証済みかどうかは、email_verify
カラムで扱うようにしていますが、「本登録済み」かなどのステータスも扱いたいと思います。
status
カラムをUser
テーブルに追加しましょう。
(email_verify
と内容が被るかもしれませんが、ここでは別々に扱うようにしています。)
migrationファイルを作成します。
$ php artisan make:migration add_column_users_table --table=users
class AddColumnUsersTable extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->tinyInteger('status')->default(0);
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('status');
});
}
}
migrate
しましょう。
config: constの追加
showForm()
でconfig('const.USER_STATUS.REGISTER')
の記述がありますが、これはステータス値をconst(定数)で管理するようにしています。
config/const.php
を作成し、以下のように設定してください。
<?php
return [
/*
|--------------------------------------------------------------------------
| Const
|--------------------------------------------------------------------------
*/
// 0:仮登録 1:本登録 2:メール認証済 9:退会済
'USER_STATUS' => ['PRE_REGISTER' => '0', 'REGISTER' => '1', 'MAIL_AUTHED' => '2', 'DEACTIVE' => '9',],
];
こうすることで、config('const.XXX')
で設定値を参照することができます。
配列のときはconfig('const.array.XXX')
のように書きます。
viewの追加
次に、Viewを追加しましょう。
showFomr()
でreturn view('auth.main.register')
と書きましたね。このviewを作成します。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">本会員登録</div>
@isset($message)
<div class="card-body">
{{$message}}
</div>
@endisset
@empty($message)
<div class="card-body">
<form method="POST" action="{{ route('register.pre_check') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">名前</label>
<div class="col-md-6">
<input
id="name" type="text"
class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}"
name="name" value="{{ old('name') }}" required>
@if ($errors->has('name'))
<span class="invalid-feedback">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="name_pronunciation"
class="col-md-4 col-form-label text-md-right">フリガナ</label>
<div class="col-md-6">
<input id="name_pronunciation" type="text"
class="form-control{{ $errors->has('name_pronunciation') ? ' is-invalid' : '' }}"
name="name_pronunciation" value="{{ old('name_pronunciation') }}"
required>
@if ($errors->has('name_pronunciation'))
<span class="invalid-feedback">
<strong>{{ $errors->first('name_pronunciation') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="name_pronunciation"
class="col-md-4 col-form-label text-md-right">生年月日</label>
<div class="col-md-6">
<div class="row">
<div class="col-md-4">
<select id="birth_year" class="form-control" name="birth_year">
<option value="">----</option>
@for ($i = 1980; $i <= 2005; $i++)
<option value="{{ $i }}"
@if(old('birth_year') == $i) selected @endif>{{ $i }}</option>
@endfor
</select>
@if ($errors->has('birth_year'))
<span class="help-block">
<strong>{{ $errors->first('birth_year') }}</strong>
</span>
@endif
</div>年
<div class="col-md-3">
<select id="birth_month" class="form-control" name="birth_month">
<option value="">--</option>
@for ($i = 1; $i <= 12; $i++)
<option value="{{ $i }}"
@if(old('birth_month') == $i) selected @endif>{{ $i }}</option>
@endfor
</select>
@if ($errors->has('birth_month'))
<span class="help-block">
<strong>{{ $errors->first('birth_month') }}</strong>
</span>
@endif
</div>月
<div class="col-md-3">
<select id="birth_day" class="form-control" name="birth_day">
<option value="">--</option>
@for ($i = 1; $i <= 31; $i++)
<option value="{{ $i }}"
@if(old('birth_day') == $i) selected @endif>{{ $i }}</option>
@endfor
</select>
@if ($errors->has('birth_day'))
<span class="help-block">
<strong>{{ $errors->first('birth_day') }}</strong>
</span>
@endif
</div>日
</div>
<div class="row col-md-6 col-md-offset-4">
@if ($errors->has('birth'))
<span class="help-block">
<strong>{{ $errors->first('birth') }}</strong>
</span>
@endif
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
確認画面へ
</button>
</div>
</div>
</form>
</div>
@endempty
</div>
</div>
</div>
</div>
@endsection
Viewでは、
- Controllerからエラーメッセージ(
$message
)があるとき- エラーメッセージのみを表示する
- Controllerからエラーメッセージがないとき
- 本会員の入力フォームを表示する
としています。
本会員カラムの追加
さて、viewで入力してもらうカラムを追加しましょう。
php artisan make:migration add_columns_users_table --table=user
今回はUsersモデルに追加していきます。
- 名前
- フリガナ
- 生年月日
「名前」はデフォルトでカラムがありますので、それ以外を追加してみましょう。
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('name_pronunciation')->nullable();
$table->integer('birth_year')->nullable();
$table->integer('birth_month')->nullable();
$table->integer('birth_day')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('name_pronunciation');
$table->dropColumn('birth_year');
$table->dropColumn('birth_month');
$table->dropColumn('birth_day');
});
}
php artisan migrate
を実行します。
確認画面の追加
ControllerにmainCheck()
を追加
確認画面のviewを返すメソッドを用意します。
RegisterController
に追加します。
public function mainCheck(Request $request)
{
$request->validate([
'name' => 'required|string',
'name_pronunciation' => 'required|string',
'birth_year' => 'required|numeric',
'birth_month' => 'required|numeric',
'birth_day' => 'required|numeric',
]);
//データ保持用
$email_token = $request->email_token;
$user = new User();
$user->name = $request->name;
$user->name_pronunciation = $request->name_pronunciation;
$user->birth_year = $request->birth_year;
$user->birth_month = $request->birth_month;
$user->birth_day = $request->birth_day;
return view('auth.main.register_check', compact('user','email_token'));
}
確認画面では入力内容を表示します。
入力内容でUsersモデルを更新したいので、viewにデータを渡し、次のControllerに渡します。
Viewの作成
register_check.blade.php
を作成します。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">本会員登録確認</div>
<div class="card-body">
<form method="POST" action="{{ route('register.main.registered') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">名前</label>
<div class="col-md-6">
<span class="">{{$user->name}}</span>
<input type="hidden" name="email" value="{{$user->name}}">
</div>
</div>
<div class="form-group row">
<label for="name_pronunciation" class="col-md-4 col-form-label text-md-right">フリガナ</label>
<div class="col-md-6">
<span class="">{{$user->name_pronunciation}}</span>
<input type="hidden" name="name_pronunciation" value="{{$user->name_pronunciation}}">
</div>
</div>
<div class="form-group row">
<label for="birth" class="col-md-4 col-form-label text-md-right">生年月日</label>
<div class="col-md-6">
<span class="">{{$user->birth_year}}年</span>
<input type="hidden" name="birth_year" value="{{$user->birth_year}}">
<span class="">{{$user->birth_month}}月</span>
<input type="hidden" name="birth_month" value="{{$user->birth_month}}">
<span class="">{{$user->birth_day}}日</span>
<input type="hidden" name="birth_day" value="{{$user->birth_day}}">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
本登録
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
ルーティングの追加
ルーティングはこんな感じです。
Route::post('register/main_check', 'Auth\RegisterController@mainCheck')->name('register.main.check');
本登録の実装
ようやく最後の処理です。
確認画面で本登録ボタンを押したら本会員登録されるようにしましょう。
ルーティングの追加
Route::post('register/main_register', 'Auth\RegisterController@mainRegister')->name('register.main.registered');
ControllerにmainRegister()
を追加
public function mainRegister(Request $request)
{
$user = User::where('email_verify_token',$request->email_token)->first();
$user->status = config('const.USER_STATUS.REGISTER');
$user->name = $request->name;
$user->name_pronunciation = $request->name_pronunciation;
$user->birth_year = $request->birth_year;
$user->birth_month = $request->birth_month;
$user->birth_day = $request->birth_day;
$user->save();
return view('auth.main.registered');
}
viewの追加
auth/main/registered.blade.php
を追加。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">本会員登録完了</div>
<div class="card-body">
<p>本会員登録が完了しました。</p>
<a href="{{url('/')}}" class="sg-btn">トップページへ戻る</a>
</div>
</div>
</div>
</div>
</div>
@endsection
完成
お疲れ様です。Laravel5.6でのメール認証は以上です。
あなたのサイトに合わせて内容を変更してみてください!
ソースコードは、こちらから参照ください!
参考
以下のサイトを参考にしています。半分くらいこちらの翻訳記事と言ってもいいかもしれません。
-
How to Use Queue in Laravel 5.4 For Email Verification
上記サイトではQueueを使用していますが、今回は使用していません。