はじめに
Laravel で有効期限付きの一次的な URL を生成してメールで送信する方法について調べた。
基本的には Laravel5.6で署名付きURL(時間制限付き)の実装が簡単に出来るようになったので試した - Qiita の内容通りで、+α として実際にメール送信(承諾/拒否)まで行っている。
TL;DR
目次
- はじめに
- TL;DR
- 環境・条件
- 詳細
- 前置き
- ルーティング
- コントローラー
- ビュー
- リクエスト
- メール
- HelloMail クラス
- 本文
- 設定
- 動作確認
- Tips
- まとめ
- 参考文献
環境・条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $ sw_vers ProductName: Mac OS X ProductVersion: 10.15.1 BuildVersion: 19B88
$ php -v PHP 7.3.9 (cli) (built: Sep 10 2019 17:45:01) ( NTS )
$ composer -V Composer version 1.9.0 2019-08-02 20:55:32
$ composer info laravel/framework name : laravel/framework versions : * v6.4.1
|
詳細
前置き
php artisan
って打つのが面倒なので art
に省略してる。
1 2 3 4 5 6 7 8
| $ type art art is a function art () { if [[ -e artisan ]]; then php artisan $@; fi }
|
ルーティング
routes/web.php
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
Route::get('/hello/create', 'HelloController@create')->name('hello.create');
Route::post('/hello', 'HelloController@send')->name('hello.send');
Route::get('/hello/hi', 'HelloController@hi')->name('hello.hi');
Route::get('/hello/bye', 'HelloController@bye')->name('hello.bye');
Route::get('/hello/invalid', 'HelloController@invalid')->name('hello.invalid');
|
コントローラー
1 2
| $ art make:controller HelloController Controller created successfully.
|
有効期限付きの一次的な URL を生成する場合は temporarySignedRoute
を使う。
以下を引数として渡す
- 対象ルート名称
- 有効期限
- 追加のパラメータ
/user/{id}
などに 'id' => 1
を渡したり
- ルーティングパラメータにない場合はクエリパラメータになったり
また、$request->hasValidSignature()
で有効期限切れや、不正 URL ではないかの検証ができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| <?php
namespace App\Http\Controllers;
use App\Http\Requests\HelloRequest; use App\Mail\HelloMail; use Illuminate\Http\Request; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\URL;
class HelloController extends Controller { public function create() { return view('hello.create'); }
public function send(HelloRequest $request) { $urls = [ 'hi' => URL::temporarySignedRoute( 'hello.hi', now()->addMinutes(1), ['from' => $request->name] ), 'bye' => URL::temporarySignedRoute( 'hello.bye', now()->addMinutes(1), ['from' => $request->name] ), ]; $mail = new HelloMail($request, $urls); Mail::to($request->email)->send($mail); return 'sent'; }
public function hi(Request $request) { if (!$request->hasValidSignature()) { return redirect()->route('hello.invalid'); } return 'hi'; }
public function bye(Request $request) { if (!$request->hasValidSignature()) { return redirect()->route('hello.invalid'); } return 'bye'; }
public function invalid() { return 'invalid'; } }
|
ビュー
入力フォーム(create)は、以下のような最小限の内容で作成。
1
| $ touch resources/views/hello/create.blade.php
|
resources/views/hello/create.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Hello</title> </head> <body> <form action="{{ route('hello.send') }}" method="POST"> @csrf <label for="name">Name</label> <input name="name" type="text" required> <br> <label for="name">email</label> <input name="email" type="email" required> <br> <label for="text">text</label> <input name="text" type="text" required> <br> <input type="submit" value="送信"> </form> </body> </html>
|
リクエスト
今回の例だと本筋に関係ないので、使わなくても良いが一応。
1 2
| $ art make:request HelloRequest Request created successfully.
|
app/Http/Requests/HelloRequest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class HelloRequest extends FormRequest { public function authorize() { return true; }
public function rules() { return [ 'name' => 'required|string', 'email' => 'required|email', 'text' => 'required|string', ]; } }
|
メール
HelloMail クラス
1 2
| $ art make:mail HelloMail Mail created successfully.
|
app/Mail/HelloMail.php
$request
, $urls
はコントローラから渡される
new HelloMail($request, $urls)
subject
に入力フォームの name
を利用
- ビューファイルで利用するパラメータを
with
で渡す
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels;
class HelloMail extends Mailable { use Queueable, SerializesModels;
public function __construct($request, $urls) { $this->request = $request; $this->urls = $urls; }
public function build() { return $this ->subject($this->request->name.'さんからメールが届きました') ->view('hello.mail.send') ->with([ 'urls' => $this->urls, 'text' => $this->request->text, ]); } }
|
本文
メール用のビューファイルを作成
1 2
| $ mkdir -p resources/views/hello/mail $ touch resources/views/hello/mail/send.blade.php
|
resources/views/hello/mail/send.blade.php
入力フォームのテキストと、承諾/拒否用 の URL のみ
1 2 3 4 5
| {{ $text }} <br> reply: {{ $urls['hi'] }} <br> ignore: {{ $urls['bye'] }}
|
設定
SMTP とかの設定を .env
で実施。Gmail で 2FA 設定してると、アプリパスワードを生成しないといけないので注意
1 2 3 4 5 6 7 8 9 10 11 12 13
| -MAIL_DRIVER=smtp -MAIL_HOST=smtp.mailtrap.io -MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_ENCRYPTION=null +MAIL_DRIVER=smtp +MAIL_HOST=smtp.gmail.com +MAIL_PORT=465 +MAIL_USERNAME=your.mail.account@gmail.com +MAIL_PASSWORD=your.mail.password +MAIL_ENCRYPTION=ssl +MAIL_PRETEND=false
|
動作確認
以下の内容でメールを送信。
こんなメールが届く。
有効期限内にアクセスすると、それぞれ hi
や bye
といった内容が表示される。
有効期限外にアクセスしたり、URL を改変したりすると invalid
が表示される。これは $request->hasValidSignature()
で検証を行って、検証 NG の場合にはリダイレクトさせているから。
Tips
middleware('signed')
を使って、ルーティング設定時に検証対象とすることもできる。
1 2 3 4
| Route::group(['middleware' => 'signed'], function() { Route::get('/hello/hi', 'HelloController@hi')->name('hello.hi'); Route::get('/hello/bye', 'HelloController@bye')->name('hello.bye'); });
|
ただし、この場合はリダイレクトとかはできない(※)ため、期限切れ URL にアクセスすると(デフォルトだと)こうなる。
※Laravel に詳しくないので本当はできるかも。
まとめ
参考文献
関連記事