0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel10でカスタムバリデーションメッセージを複数設定する方法

Last updated at Posted at 2023-08-31

はじめに

パスワードのバリデーションを例に挙げます。

'password' => 'required|min:8|max:128|alpha_dash', のようにすれば、
とりあえず最低限のバリデーションは実装できます。
ですが、alpha_dash は全角文字が通るという、ちょっと嬉しくない仕様になっています。

まあパスワードに全角文字を使う酔狂なユーザーはいないと思うので、無視してもいい気もしますが、
うちは結構、そのあたりうるさいので、より厳格なパスワードバリデーションを実装します。

ただ、「パスワードが正しくありません」と出すだけでは不親切ですよね?
パスワードの正しくない箇所に応じて、スクショのように入力内容に応じたエラーメッセージを出すようにします。

image.png

パスワードのルール

  • 8文字以上、128文字以下であること
  • 少なくとも1文字以上、半角のアルファベット小文字を含むこと
  • 少なくとも1文字以上、半角のアルファベット大文字を含むこと
  • 少なくとも1文字以上、半角数字を含むこと
  • 少なくとも1文字以上、右記の記号のいずれかを含むこと ( !?@#$%^&*()\-_=+{};:,<.>~ )

実装

こんな風にする

cd /path/to/project_dir/
php artisan make:rule StrongPassword
php artisan make:request AuthRequest
php artisan make:controller AuthController --model=User
touch resources/views/auth/login.blade.php
app\Rules\StrongPassword.php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class StrongPassword implements ValidationRule
{
    /**
     * バリデーションルールの実行
     * NOTE: Laravel 9系までと10系ではバリデーションの書き方が大きく異なっているので
     * 必ずリファレンスを確認すること
     * 
     * @see https://readouble.com/laravel/10.x/ja/validation.html#custom-validation-rules
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $regexLowercase = '/[a-z]/';
        $regexUppercase = '/[A-Z]/';
        $regexNumber = '/[0-9]/';

        $specialPattern = '!?@#$%^&*()\-_=+{};:,<.>~';
        $regexSpecial = "/[{$specialPattern}]/";

        $hasLowerCase = (preg_match_all($regexLowercase, $value) > 0);
        $hasUpperCase = (preg_match_all($regexUppercase, $value) > 0);
        $hasNumber = (preg_match_all($regexNumber, $value) > 0);
        $hasSpecial = (preg_match_all($regexSpecial, $value) > 0);

        if(! $hasLowerCase) $fail(':attributeは少なくとも1文字以上、半角英小文字を含む必要があります');
        if(! $hasUpperCase) $fail(':attributeは少なくとも1文字以上、半角英大文字を含む必要があります');
        if(! $hasNumber) $fail(':attributeは少なくとも1文字以上、半角数字を含む必要があります');
        if(! $hasSpecial) $fail(":attributeは少なくとも1文字以上、右記の記号のいずれかを含む必要があります {$specialPattern}");
    }
}
app\Http\Requests\AuthRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

use App\Rules\StrongPassword;

class AuthRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'email' => ['required','email','max:255'],
            'password' => ['required','min:8','max:128', new StrongPassword()],
        ];
    }
}
app\Http\Controllers\AuthController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

use App\Models\User;

use App\Http\Requests\AuthRequest;

class AuthController extends Controller
{
    use AuthorizesRequests, ValidatesRequests;

    public function login()
    {
        $viewName = Route::currentRouteName();
        return view($viewName);
    }

    public function loginAuth(AuthRequest $request)
    {
        $email = $request['email'];
        $password = $request['password'];

        $is_auth = false;
        $user = User::where('email', $email)->first();
        if ($user != null) {
            if (password_verify($password, $user->password)) {
                $is_auth = true;  // パスワード認証成功
            }
        }

        if ($is_auth) {
            session()->forget('user');
            session()->push('user', $user);

            $routeName = 'admin.top';
            $message = trans('messages.login');
            return redirect()->route($routeName)->with('success', $message);
        }
        else {
            $res = back()->withErrors([
                'password' => trans('auth.invalid')
            ]);

            return $res;
        }
    }
}
resources/views/auth/login.blade.php
@if(session('success'))
      <div id="alert-message" class="alert alert-success">{{ session('success') }}</div>
@endif
@if ($errors->any())
      <div id="alert-messages" class="alert alert-danger">
        <ul>
    @foreach ($errors->all() as $error)
          <li>{{ $error }}</li>
    @endforeach
        </ul>
      </div>
@endif
      {{ Form::open(['url' => 'login', 'method' => 'post']) }}
          {{ Form::email('email', '') }}
          {{ Form::password('password') }}
          <button type="submit">{{ __('action.login') }}</button>
      {{ Form::close() }}
@endsection

※ViewにはLaravel Collectiveを使用しています

ポイント

バリデーションメッセージを複数出すには、 $fail コールバックを使います。

$fail(':attributeは少なくとも1文字以上、半角英小文字を含む必要があります'); みたいな感じのやつを書けば、
エラーメッセージを複数ストックできますので、Viewで @foreach ($errors->all() as $error) のループで回すことで全てのエラーを表示可能です。

追記

パスワードはパスワードルールオブジェクトというものを使えば
自前で実装する必要がなかったです。
すいません知りませんでした (*ノωノ)

app\Http\Requests\AuthRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

use Illuminate\Validation\Rules\Password;

class AuthRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        // パスワードルールオブジェクトについて
        // https://readouble.com/laravel/10.x/ja/validation.html#validating-passwords
        // https://laravel.com/api/10.x/Illuminate/Validation/Rules/Password.html
        return [
            'email' => ['required','email','max:255'],
            'password' => [
                'required',
                'max:255',
                Password::min(8)
                ->letters()   // 最低1文字の文字が必要
                ->mixedCase() // 最低大文字小文字が1文字ずつ必要
                ->numbers()   // 最低1文字の数字が必要
                ->symbols()   // 最低1文字の記号が必要 右記の記号は通ることを確認済 !?@#$%^&*()\-_=+{};:,<.>~
                ->uncompromised(3), // 右記サイトでデータ漏洩の実績が3回以下のパスワードであること https://haveibeenpwned.com/Passwords
            ],
        ];
    }
}
resources/lang/ja/validation.php
<?php

return [
    'password' => [
        'letters' => ':attributeは、少なくとも1つの文字が含まれていなければなりません。',
        'mixed' => ':attributeは、少なくとも大文字と小文字を1つずつ含める必要があります。',
        'numbers' => ':attributeは、少なくとも1つの数字が含まれていなければなりません。',
        'symbols' => ':attributeは、少なくとも1つの記号が含まれていなければなりません。',
        'uncompromised' => 'この:attributeは過去に漏洩したことのある脆弱な:attributeです。別の:attributeを入力してください。',
    ]
];

さらに追記

他フィールドの値を取得するには、DataAwareRule を使用するようにして、setDataで値をセットすれば利用可能になります。

app/Rules/ValidDate.php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

use Illuminate\Contracts\Validation\DataAwareRule; // ★追記

class ValidDate implements DataAwareRule, ValidationRule
{
    /**
     * バリデーション下の全データ
     *
     * @var array<string, mixed>
     */
    protected $data = [];

    /**
     * バリデーション下のデータをセット
     *
     * @param  array<string, mixed>  $data
     */
    public function setData(array $data): static
    {
        $this->data = $data;

        return $this;
    }

    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $year = $this->data['year'];
        $month = $this->data['month'];
        $day = $value;

        $isValidDate = checkdate($month, $day, $year);

        if(! $isValidDate) $fail("設定された日付は正しくありません");
    }
}

さらにさらに追記

パスワードルールに違反した場合、それぞれのエラーメッセージを出すのはユーザーフレンドリーである一方で、悪意ある第三者にパスワードルールを予測され、攻撃されるセキュリティリスクを増大させる恐れがあります。
そのため、パスワードルールに抵触した場合は単に「メールアドレス、またはパスワードが違います。」とだけ表示するように改修します。

app\Http\Requests\AuthRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

use Illuminate\Validation\Rules\Password;

class AuthRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        // パスワードルールオブジェクトについて
        // https://readouble.com/laravel/10.x/ja/validation.html#validating-passwords
        // https://laravel.com/api/10.x/Illuminate/Validation/Rules/Password.html
        return [
            'email' => ['required','email','max:255'],
            'password' => [
                'required',
                'max:255',
                Password::min(8)
                ->letters()   // 最低1文字の文字が必要
                ->mixedCase() // 最低大文字小文字が1文字ずつ必要
                ->numbers()   // 最低1文字の数字が必要
                ->symbols()   // 最低1文字の記号が必要 右記の記号は通ることを確認済 !?@#$%^&*()\-_=+{};:,<.>~
                ->uncompromised(3), // 右記サイトでデータ漏洩の実績が3回以下のパスワードであること https://haveibeenpwned.com/Passwords
            ],
        ];
    }

    public function messages()
    {
        return [
            'password.required' => trans('validation.required'),
            'password.max' => trans('validation.max.string'),
            'password.*' => trans('auth.invalid'),
        ];
    }
}
resources/lang/ja/auth.php
<?php

return [
    'invalid' => 'メールアドレス、またはパスワードが違います。'
];
resources/lang/ja/validation.php
<?php

return [
    'max' => [
        'numeric' => ':attributeには:max以下の数値を指定してください。',
        'file'    => ':attributeには:max KB以下のファイルを指定してください。',
        'string'  => ':attributeには:max文字以下の文字列を指定してください。',
        'array'   => ':attributeには:max個以下の要素を持つ配列を指定してください。',
    ],
    'required' => ':attributeは必須です。',
    'password' => [
        'letters' => ':attributeは、少なくとも1つの文字が含まれていなければなりません。',
        'mixed' => ':attributeは、少なくとも大文字と小文字を1つずつ含める必要があります。',
        'numbers' => ':attributeは、少なくとも1つの数字が含まれていなければなりません。',
        'symbols' => ':attributeは、少なくとも1つの記号が含まれていなければなりません。',
        'uncompromised' => 'この:attributeは過去に漏洩したことのある脆弱な:attributeです。別の:attributeを入力してください。',
    ]
];
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?