はじめに
現在SIer5年目でjavascript(Jqueryのみ)、PHP(フレームワーク無し)を2年ほど、C#(Windowsアプリ)3年ほどやってきました。
色々なご縁があり、個人で最近Webサービスの立ち上げをやることになったのですが何せ本当にWebサービスを立ち上げるための知識がほぼ0に等しいです
ただ今後のキャリアを考えた時に今のままではいけないと思いチャレンジすることにしました。
まずは最初に技術を習得しないといけないので、学ぶ&アウトプットするために毎回投稿していこうと思います。
今後身についていこうと思ってるのは下記のような技術です。
AWS
Docker
CI/CD環境の構築
Laravel
Nuxt.js
今回はLaravel+Nuxtについて学んでいきます。
今回学ぶこと
Laravel+Nuxtでのメール認証機能とパスワードリセット機能を作成していこうかと思います。
今回はLaravel API側の実装をしていきます
参考サイト
メール認証の概要は個々がわかりやっすかったです。
前提
Laravel 5.8
Nuxt 2.5.4
jwt-auth
ログイン機能などはこちらで実装してるのでこちらを参照ください
パスワードリセット機能の実装
php artisan make:auth
で作成されたForgotPasswordController
をAPI用にカスタマイズしていきます
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
public function __construct()
{
$this->middleware('guest');
}
public function sendResetLinkEmail(Request $request)
{
//メールリセットのリクエストをチェックする
$validate = $this->validateEmail($request->all());
if ($validate->fails()) {
return new JsonResponse('Email is Invalid');
}
$response = $this->broker()->sendResetLink(
$request->only('email')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
//メールアドレスチェック
protected function validateEmail(array $data)
{
return Validator::make($data, [
'email' => 'required|email',
]);
}
protected function sendResetLinkResponse(): JsonResponse
{
return new JsonResponse('Send Reset Mail');
}
protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse
{
return new JsonResponse(trans($response));
}
}
Nuxt側でのパスワードリセットURLを指定する
<?php
return [
'url' => env('FRONTEND_URL', 'http://localhost:3000'), //フロントエンドのURL
'reset_pass_url' => env('RESET_PASS_URL', '/reset?queryURL='), // フロントエンドのパスワードリセットページのURL
];
パスワードリセットのメールを呼び出す関数sendPasswordResetNotification
を追加する
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
+ use App\Notifications\CustomPasswordReset;
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
+ public function sendPasswordResetNotification($token)
+ {
+ $this->notify(new CustomPasswordReset($token));
+ }
}
パスワードリセットメール通知用のclassを実装する
<?php
namespace App\Notifications;
use App;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class CustomPasswordReset extends Notification
{
use Queueable;
public $token;
public function __construct($token)
{
$this->token = $token;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new MailMessage)
->subject('パスワードリセット') // 件名
->view('emails.resetpass') // メールテンプレートの指定
->action('リセットパスワード',
config('frontend.url') . config('frontend.reset_pass_url') .
url('api/password/reset', $this->token) //アクセスするURL
);
}
}
メールテンプレートの作成を行う
<h3>
<a href="{{ config('app.url') }}">{{ config('app.name') }}</a>
</h3>
<p>
{{ __('Click link below and reset password.') }}<br>
{{ __('If you did not request a password reset, no further action is required.') }}
</p>
<p>
{{ $actionText }}: <a href="{{ $actionUrl }}">{{ $actionUrl }}</a>
</p>
ルートの定義を追加する
Route::group(['prefix' => '/auth', ['middleware' => 'throttle:20,5']], function (){
Route::post('/register', 'Auth\RegisterController@register');
Route::post('/login', 'Auth\LoginController@login');
+ Route::post("/password/email", "Auth\ForgotPasswordController@sendResetLinkEmail");
+ Route::post("/password/reset/{token}", "Auth\ResetPasswordController@reset");
});
メール送信テスト
今回は、mailtrapというサービスを使用して簡単にローカルでもメール送信テストをしていきたいと思います
.env
参考サイトを元に記載しました。
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=XXXXX
MAIL_PASSWORD=XXXXX
MAIL_ENCRYPTION=null
PostManでテストする
mailtrapでも送信ができていることを確認する
パスワードリセットするリクエストを実行するAPIを実装
<?php
namespace App\Http\Controllers\api\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
+ use Illuminate\Http\Request;
+ use Illuminate\Support\Str;
+ use Illuminate\Support\Facades\Validator;
+ use Illuminate\Support\Facades\Password;
+ use Illuminate\Auth\Events\PasswordReset;
+ use Illuminate\Contracts\Auth\PasswordBroker;
+ use Illuminate\Http\JsonResponse;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
+ public function reset(Request $request)
+ {
+ $validate = $this->validator($request->all());
+
+ if ($validate->fails()) {
+ return new JsonResponse($validate->errors());
+ }
+ $response = $this->broker()->reset(
+ $this->credentials($request), function ($user, $password) {
+ $this->resetPassword($user, $password);
+ }
+ );
+ return $response == Password::PASSWORD_RESET
+ ? $this->sendResetResponse($request, $response)
+ : $this->sendResetFailedResponse($request, $response);
+ }
+ protected function resetPassword($user, $password)
+ {
+ $user->forceFill([
+ 'password' => bcrypt($password),
+ 'remember_token' => Str::random(60),
+ ])->save();
+ }
+ protected function sendResetResponse(Request $request, $response)
+ {
+ return new JsonResponse('Password Reset');
+ }
+ protected function sendResetFailedResponse(Request $request, $response)
+ {
+ return new JsonResponse($response);
+ }
+ protected function validator(array $data)
+ {
+ return Validator::make($data, [
+ 'token' => 'required',
+ 'email' => 'required|email',
+ 'password' => 'required|confirmed|min:6',
+ ]);
+ }
+ }
PostManでパスワードが変更できるのか確認する
メール認証機能の実装
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
use App\Notifications\CustomPasswordReset;
+ use App\Notifications\VerifyEmail;
- class User extends Authenticatable implements JWTSubject
+ class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
public function sendPasswordResetNotification($token)
{
$this->notify(new CustomPasswordReset($token));
}
+ public function sendEmailVerificationNotification()
+ {
+ $this->notify(new VerifyEmail);
+ }
}
メール認証のURLを生成するVerifyEmail
を作成する
<?php
namespace app\Notifications;
use App;
use Illuminate\Auth\Notifications\VerifyEmail as VerifyEmailBase;
use Illuminate\Support\Facades\URL;
use Carbon\Carbon;
class VerifyEmail extends VerifyEmailBase
{
protected function verificationUrl($user)
{
$prefix = config('frontend.url') .config('frontend.email_verify_url');
$routeName = 'verification.verify';
$temporarySignedURL = URL::temporarySignedRoute(
$routeName, Carbon::now()->addMinutes(60), ['id' => $user->getKey()]
);
return $prefix . urlencode($temporarySignedURL);
}
}
Nuxt側の認証画面URLを定義する
<?php
return [
'url' => env('FRONTEND_URL', 'http://localhost:3000'), //フロントエンドのURL
'reset_pass_url' => env('RESET_PASS_URL', '/reset?queryURL='), // フロントエンドのパスワードリセットページのURL
+ 'email_verify_url' => env('FRONTEND_EMAIL_VERIFY_URL', '/verify?queryURL='), // メール認証のURL
];
認証APIのルートも定義する
Route::group(['prefix' => '/auth', ['middleware' => 'throttle:20,5']], function (){
Route::post('/register', 'Auth\RegisterController@register');
Route::post('/login', 'Auth\LoginController@login');
Route::post("/password/email", "Auth\ForgotPasswordController@sendResetLinkEmail");
Route::post("/password/reset/{token}", "Auth\ResetPasswordController@reset");
+ Route::get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
+ Route::post('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
});
ユーザ登録時にメール認証のためのメール送信を行うためにRegisterController
を修正する
- $user = $this->create($request->all());
+ event(new Registered($user = $this->create($request->all())));
認証を完了するAPIを実装する
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Verified;
use App\User;
use Illuminate\Http\JsonResponse;
class VerificationController extends Controller
{
use VerifiesEmails;
public function __construct()
{
$this->middleware('throttle:6,1');
}
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!$user->email_verified_at) {
$user->markEmailAsVerified();
event(new Verified($user));
return new JsonResponse('Email Verified');
}
return new JsonResponse('Email Verify Failed');
}
public function resend(Request $request)
{
$user = User::where('email', $request->get('email'))->get()->first();
if (!$user) {
return new JsonResponse('No Such User');
}
if ($user->hasVerifiedEmail()) {
return new JsonResponse('Already Verified User');
}
$user->sendEmailVerificationNotification();
return new JsonResponse('Send Verify Email');
}
}
認証用のURLを叩いて、DBのemail_verified_at
が日時が入っていれば認証完了となる
まとめ
基本的には、Laravelの機能をAPI用に改造するだけで実装ができてしまうので、本当に便利でした。
次回はNuxtの画面側の実装もしていきたいと思います。