Đăng nhập với nhiều tùy chọn tên đăng nhập
Bài đăng này đã không được cập nhật trong 5 năm
Xin chào các bạn!
Hôm nay chúng ta cùng nhau bàn luận về chủ đề xác thực người dùng trong laravel. Đối với một website thì việc xác thực người dùng là một công việc rất quan trong và được đặt lên hàng đầu. Laravel đã hỗ trợ sẵn phương thức xác thực người dùng vô cùng mạnh mẽ và việc thay đổi nó theo mục đích sử dụng của dự án rất dễ dàng.
Khi xây dựng tính năng xác thực người dùng bạn có thể gặp các vấn đề sau:
- Đăng nhập với nhiều tùy chọn tên đăng nhập: đăng nhập bằng email hoặc số điện thoại.
- Có nhiều vai trò người dùng khác nhau trong hệ thống: admin, user
- Chuyến hướng sau khi đăng nhập
- Giới hạn yêu cầu đăng nhập
Trong phạm vi bài viết này chúng ta cùng xây dựng một tính năng đăng nhập với hai tùy chọn tên đăng nhập hoặc là địa chỉ email hoặc là số điện thoại.
Đặt vấn đề
Một trang đăng nhập thông thường sẽ có một input là email và một input là mật khẩu. Nhưng khi người dùng có nhiều thông tin có thể xác thực người dùng đó: email, username, số điện thoại. Tất yếu phải có nhiều lựa chọn hơn để đăng nhập.
Như trang đăng nhập của viblo chẳng hạn.
Ta có có thể nhập email hoặc username để đăng nhập.
Hay là facebook ta có thể nhập địa chỉ email hoặc số điện thoại để đăng nhập vào hệ thống.
Giải pháp
- Sử dung regex để xác định định dạng dữ liệu tên đăng nhập mà người dùng nhập vào xem đó là số điện thoại hay email
- Sau đó đăng nhập bằng loại dữ liệu xác định được
Cách thực hiện
Chúng ta cần chuẩn bị một project laravel mới. Ở đây mình dùng laravel phiên bản 5.7.
Chạy lệnh tạo view và route auth mặc định của laravel
php artisan make:auth
Thêm các thông tin người dùng
Thêm thông tin số điện thoại vào bảng người dùng.
// database/migrations/2014_10_12_000000_create_users_table.php
//................................
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique()->nullable();
$table->string('phone', 20)->unique()->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
//................................
Mình thêm một cột số điện thoại phone
và có sửa cột email
để có thể có giá trị.
Mục đích của việc này là giúp cho việc đăng ký chỉ cần một trong hai thông tin email hoặc số điện thoại là được.
Bạn chạy php artisan migrate
hoặc php artisan migrate:refresh
để cập nhật bảng cơ sở dữ liệu nếu lỡ tay chạy migrate trước đó rồi.
Chú ý: Khi chạy lệnh php artisan migrate:refresh
thì tất cả các dữ liệu của bảng sẽ bị xóa hết.
Cuối cùng là thêm cột phone vào User model
// app/Models/User.php
//................................
protected $fillable = [
'name',
'email',
'phone',
'password',
];
//................................
Chú ý: Mình di chuyển tất cả models vào thư mục Models để đễ quản lý.
Nếu bạn làm như mình thì hay chắc chắn rằng đã đổi namespace của model từ namespace App;
thành namespace App\Models;
và sửa trong config/auth.php
.
// config/auth.php
//................................
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\Models\User::class,
],
],
//................................
Các hàm bổ trợ
Số điện thoại thì có rất nhiều dạng thù hình khác nhau: 0912345678, 84912345678, +84912345678, 0912 345 678, 0912-345-678,... Ta cần phải chuyển chúng vào cùng một dạng duy nhất là: 84912345678 (đây là số điện thoại Việt Nam, nước khác thì sẽ có mã quốc gia khác, trong phần này mình chỉ nói về số điện thoại Việt Nam cho đơn giản).
Mình sử dụng một hàm helper tự thêm như sau:
// app/Helper/GlobalFunction.php
//................................
if (!function_exists('format_phone_number')) {
function format_phone_number($phoneNumber, $country = null)
{
if (!$phoneNumber || (!is_string($phoneNumber) && !is_integer($phoneNumber))) {
return null;
}
$countryCodes = config('country.codes');
$defaultCountry = 'VN';
//Remove any parentheses and the numbers they contain:
$phoneNumber = preg_replace('/\([0-9]+?\)/', '', $phoneNumber);
//Strip spaces and non-numeric characters:
$phoneNumber = preg_replace('/[^0-9]/', '', $phoneNumber);
//Strip out leading zeros:
$phoneNumber = ltrim($phoneNumber, '0');
if (!$phoneNumber) {
return $phoneNumber;
}
//Look up the country dialling code for this number:
if ($country && array_key_exists($country, $countryCodes)) {
$pfx = $countryCodes[$country];
} else {
$pfx = $countryCodes[$defaultCountry];
}
//Check if the number doesn't already start with the correct dialling code:
if (!preg_match('/^' . $pfx . '/', $phoneNumber)) {
$phoneNumber = $pfx . $phoneNumber;
}
return $phoneNumber;
}
}
Trong hàm này mình có sử dụng config country.codes
. Nội dung file config/country.php
như sau
// config/country.php
return [
'codes' => [
'AC' => '247',
'AD' => '376',
'AE' => '971',
'AF' => '93',
'AG' => '1268',
//.................
'VN' => '84'
//.................
],
];
Chú ý: để thêm helper cho project laravel bạn cần phải khai báo trong phần loader của composer.json và chạy composer dump-autoload
hoặc composer install
// composer.json
//................................
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Helper/GlobalFunction.php"
]
},
//................................
Lập trình phần đăng ký
Phần hiển thị (view)
Thêm trường số điện thoại vào dưới email trong form đăng ký:
<!-- resources/views/auth/register.blade.php -->
<!-- ................................ -->
<div class="form-group row">
<label for="phone" class="col-md-4 col-form-label text-md-right">{{ __('Phone number') }}</label>
<div class="col-md-6">
<input id="phone" type="text" class="form-control{{ $errors->has('phone') ? ' is-invalid' : '' }}" name="phone" value="{{ old('phone') }}">
@if ($errors->has('phone'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('phone') }}</strong>
</span>
@endif
</div>
</div>
<!-- ................................ -->
Hiển thị lên trình duyệt sẽ như sau:
Phần điều khiển (controller)
Phần kiểm tra dữ liệu đăng ký đầu vào. Sửa hàm validator
trong RegisterController
.
// app/Http/Controllers/Auth/RegisterController.php
//................................
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required_without:phone|string|email|max:255|unique:users',
'phone' => 'required_without:email|max:20|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
//................................
Dữ liệu đăng ký phải có một trong hai trường hoặc có email
hoặc có phone
. Nên mình để điều kiện email
phải required_without:phone
và ngược lại.
Ta phải thêm trường phone
trong hàm tạo người dùng (create
). phone
phải được format đúng định dạng trước khi thêm vào.
// app/Http/Controllers/Auth/RegisterController.php
//................................
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'phone' => format_phone_number($data['phone']),
'password' => Hash::make($data['password']),
]);
}
//................................
Lập trình phần đăng nhập
Phần hiển thị
Thay trường nhập email
thành nhập email hoặc số điện thoại tên của trường này mình sẽ để là username
<!-- app/Http/Controllers/Auth/RegisterController.php -->
<!-- ................................ -->
<div class="form-group row">
<label for="username" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail Address or Phone number') }}</label>
<div class="col-md-6">
<input id="username" type="username" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required autofocus>
@if ($errors->has('username'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('username') }}</strong>
</span>
@endif
</div>
</div>
<!-- ................................ -->
Hiển thị ra sẽ như sau:
Phần điều khiển (controller)
Phần điều khiển của chức năng đăng nhập sẽ nằm tòan bộ ở app/Http/Controllers/Auth/LoginController.php
. Controller này sẽ sử dụng trait Illuminate\Foundation\Auth\AuthenticatesUsers
.
Khi mở AuthenticatesUsers
trait ra thì có rất nhiều hàm xử lý. Luồng xử lý khi đăng nhập sẽ như sau:
- Validate dữ liệu
- Kiểm tra xem người dùng có đăng nhập quá nhiều lần thất bại không
- Thực hiện đăng nhập
- Khi người dùng đăng nhập thành công thì tạo lại session, xóa bộ đếm số lần đăng nhập không thành công, chuyến hướng người dùng mặc định là đến trang chủ.
- Khi người dùng đăng nhập thất bại thì sẽ tăng biến đếm số lần đăng nhập nếu người dùng đăng nhập không thành công đồng thời chuyến hướng người dùng lại trang đăng nhập kèm thông báo lỗi.
Đầu tiên ta phải viết lại hàm username
. Thay đổi giá trị trả về của nó thành username
vì trường gửi lên sẽ có tên là username
.
Để thực hiện được đăng nhập bằng nhiều kiểu dữ liệu chúng ta cần xử lý ở hàm credentials
.
Đầu tiên sẽ kiểm tra username có phải là email không qua regex. Mình sử dụng email regex trên trang https://emailregex.com
const EMAIL_REGEX = '/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD';
Nếu không phải email thì có thể là số điện thoại.
Hàm credentials
sẽ như sau:
// app/Http/Controllers/Auth/LoginController.php
//................................
protected function credentials(Request $request)
{
$username = $request->get($this->username(), '');
$usernameField = 'email';
preg_match(self::EMAIL_REGEX, $username, $matches);
// Nếu không phải địa chỉ email thì sẽ có thể là số điện thoại
if (empty($matches)) {
$usernameField = 'phone';
$username = $this->validatePhoneNumber($username);
}
return [
$usernameField => $username,
'password' => $request->get('password'),
];
}
protected function validatePhoneNumber($username)
{
$username = format_phone_number($username);
if ($username) {
return $username;
}
// nếu không phải số điện thoại thì trả về thông báo lỗi
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
]);
}
//................................
Như vậy là xong rồi. Bạn thử kiểm tra xem có thể đăng nhập bằng địa chỉ email hoặc số điện thoại không nhé.
Cảm ơn bạn đã đọc bài viết của mình. Hi vọng bài viết có thể giúp ích cho bạn.
All rights reserved