今回はメールアドレスの変更APIを作成してみます。
普通にユーザーに対してupdateしたらよいんじゃないか?って声は聞こえてきそうですが、そうはいかないのです。
なぜなら…?
Table of Contents
伝えたいこと: メールアドレス変更API作成の方法
まず、なぜメールアドレスの変更を行うのか。基本的にwebサービスではユーザー登録を行った際にemail verifyが行われますよね?
email verifyとは、「登録したメールアドレスってちゃんとあなたが使っていますよね?」って確認するやつです。
ユーザー登録を行うと、メールが届いてurlにアクセスしてください。っていうのが基本的ですがそれです。
そのemail verifyが行われたメールアドレスから別のアドレスに変更する際に、普通にユーザー情報の更新と一緒にemail verify無しで更新しちゃったら…
ダミーメールアドレスかもしれないですよね?
それを防ぐためにも、メールアドレスを変更するときにはそれ専用のAPIが必要となってくるわけです。
そして今回メールアドレスを変更する流れは以下です。
- ユーザーがメールアドレス変更の申請を行う。(新メールアドレスを送信)
- 新メールアドレスに対してtokenを発行して、新メールアドレスにメールを送る。(今回メールを送る機能は作りません。)
- ユーザーがメールから届いたurlにアクセスするとメールアドレスの更新は完了。
この流れをAPI的に作ってみましょう。
前提条件
以下を前提として進めていきますね。
- Laravel8
- APIをつかった方法
- メール送信の機能は作成しない
- すでにuserテーブルは作成されていると仮定
- 認証機能は作成済み(sanctum)
メールアドレス変更の申請API
今回はこちらの記事を参考にさせていいただいています。
新メールアドレスとtokenを保存するテーブルを作成
ほい、それでは作っていきましょうか。
まず作りたいのは、まずは新メールアドレスを保存するテーブルを作らないといけませんね。
最終的にここで作成するテーブルを見てユーザー情報を更新します。user_email_resets
テーブルを作成しましょう。
php artisan make:migration create_user_email_resets_table
Code language: CSS (css)
ではテーブルの中身を書いていきます。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserEmailResetsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//userがメールアドレスを変更する時に利用するテーブル
Schema::create('user_email_resets', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->comment('users.id');
$table->string('new_email')->comment('ユーザーが新規に設定するメールアドレス');
$table->string('token')->unique()->comment('email address変更用token');
$table->dateTime('expired_at')->nullable()->comment('tokenの有効期限');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_email_resets');
}
}
Code language: HTML, XML (xml)
こんな感じで作ります。
正直tokenの有効期限はcreated_atを見てそこから何時間か計算したりしてもよいんですが、今回は有効期限用のカラムも準備しました。そっちのほうが個人的にシンプルに見える!
そしたらテーブルを作成っと
php artisan migrate
モデルを作成
先ほど作成したテーブルに付随するモデルを作成しましょう。
php artisan make:model UserEmailReset
Code language: CSS (css)
モデルには今回大したことは書いていません。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserEmailReset extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'new_email',
'token',
'expired_at',
];
}
Code language: HTML, XML (xml)
メールアドレス変更を申請するControllerの作成
php artisan make:controller Change
Code language: CSS (css)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
//追加
use App\Http\Requests\EmailReset\CreateTokenUserEmailResetRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Models\UserEmailReset;
use App\Models\User;
class ChangeUserEmailController extends Controller
{
public function createToken(CreateTokenUserEmailResetRequest $request)
{
//token生成
$token = hash_hmac(
'sha256',
Str::random(40),
config('app.key')
);
//メール変更したいユーザー取得
$user_id = Auth::id();
//createメソッドを使ってレコードを追加する。
$reset_info = UserEmailReset::create([
'user_id' => $user_id,
'new_email' => $request->email,
'token' => $token,
'expired_at' => date("Y-m-d H:i:s", strtotime("+1 day", time())), //tokenの有効期限は作成日から24時間
]);
//成功をレスポンスで返す。
return response()->json([
'success' => true,
'message' => 'Create email reset token success!',
'details' => $reset_info->token
]);
}
}
Code language: HTML, XML (xml)
とりあえずtokenの有効期限は1日にしておきました。1時間とかのほうが良いんかなぁ…?
hash_hmac()の使い方
FormRequest
一応バリデーションはformrequestに書いていきます。
php artisan make:request EmailReset/CreateTokenUserEmailResetRequest
ほんでバリデーション
<?php
namespace App\Http\Requests\UserEmailReset;
use Illuminate\Foundation\Http\FormRequest;
//追加
use Illuminate\Validation\Rule;
class CreateTokenUserEmailResetRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')],
];
}
}
Code language: HTML, XML (xml)
今回の場合は変更後のメールアドレスをリクエストパラメータに入れて送られてくるので、email
というパラメータをバリデーションしましょう。
これでOK後はroutingに追加。
api.php(routing)に追加
use App\Http\Controllers\ChangeUserEmailController;
//sanctum認証
Route::middleware('auth:sanctum')->group(function(){
Route::post('user/email-reset', [ChangeUserEmailController::class, 'createToken']);
});
Code language: PHP (php)
なんで認証入れてるのかというのは、controllerのところでAuth::id()
を利用しているからですね~
認証済みのユーザーからユーザー情報を取得しています。
リクエストパラメータ
今作ったAPIで送るリクエストは、新しくしたいメールアドレスをリクエストパラメータに入れて送ります。
POST api/user/email-reset
{
email: new_email@email.com
}
Code language: CSS (css)
レスポンスには、アクティベート用のtokenが返ってきます。
その返ってきたtokenを使って次のAPIでアクティベートをかけます。
メールアドレス変更を完了するAPI
ChangeUserEmailControllerに追記
そしたら次はemailを変更する機能を作りましょう。
public function resetEmail($token)
{
//送られてきたtokenが存在しない場合エラーを返す
if(!UserEmailReset::where('token', $token)->exists()){
return response()->json([
'success' => false,
'message' => 'token does not exist',
'details' => $token,
]);
}
$email_reset = UserEmailReset::where('token', $token)->first();
//tokenが存在&tokenの期限が切れていない場合はuser_email_resetsテーブルをみて
//new_emailを対象のユーザーのメールアドレスとして更新する。
if($email_reset && $email_reset->expired_at >= now()) {
$user = User::where('id', $email_reset->user_id)->first();
$user->email = $email_reset->new_email;
$user->save();
//updateできたらレコードを削除する
UserEmailReset::where('token', $token)->delete();
return response()->json([
'success' => true,
'message' => 'Update email address success!',
'details' => $user
]);
}else{
//対象のレコードのtokenの期限が切れているとレコードを削除する。ユーザーは再設定が必要。
UserEmailReset::where('token', $token)->delete();
return response()->json([
'success' => false,
'message' => 'your token has expired',
'details' => $email_reset,
]);
}
}
Code language: PHP (php)
今回はGETパラメーターに先ほど作成したtokenが送られてくるのでそのtokenから情報を取得してユーザーのメールアドレスをupdateさせます。
まず最初に以下の動作を行います。
- リクエストで送られてきたtokenがuser_email_resetsテーブルに存在するか確認
- tokenの有効期限は切れていないか確認
問題がなさそうだったら、user_email_resetsテーブルにuser_idカラムがあるのでusersテーブルのidカラムと一致するデータを取得します。
そして最後にメールアドレスをuser_email_resets.new_emailからメールアドレスを取得して、users.emailにupdateします!
そうするとアクティベートの完成!
アクティベートができたらuser_email_resetsの対象レコードを削除しておきます!
routing (api.php)
routingに以下を追加します。
Route::get('user/email-reset/{token}', [ChangeUserEmailController::class, 'resetEmail']);//新規メールアドレスに更新
Code language: PHP (php)
リクエストはGETリクエストでGETパラメータに先ほどのresetのAPIで作成したtokenを入れて下さい。
まとめ
意外とシンプルにできましたね。
laravelってデフォルトでメールアドレス変更する用の機能ないんかなぁって思いました。
あったらすみません。