Laravel 9 2FA - Authy를 사용한 이중 인증

원래 @https://codeanddeploy.com에 게시된 샘플 코드를 방문하여 다운로드합니다.
https://codeanddeploy.com/blog/laravel/laravel-9-2fa-two-factor-authentication-with-authy

이 게시물에서는 Authy를 사용하여 Laravel 8, 9 2FA - 2단계 인증을 구현하는 방법을 공유할 것입니다. 우리는 2단계 인증이 다른 사람이 사용자 자격 증명을 얻고 계정에 액세스하는 경우 애플리케이션 보안의 추가 계층이라는 것을 알고 있습니다. 이 구현을 사용하면 인증을 계속하기 전에 다른 확인이 필요하기 때문에 사용자 계정에 액세스하는 것이 확실히 쉽지 않을 것입니다.



이 튜토리얼에서는 Laravel Two Factor 인증을 위해 Authy 앱을 사용하고 이를 수행하는 방법을 단계별로 보여드리겠습니다.

시작하기 전에 프로세스를 단축할 수 있도록 Laravel 9 인증에 대한 이전 튜토리얼을 다운로드해야 합니다. 그러나 Laravel 인증이 이미 있는 경우 이를 건너뛰고 Laravel Two Factor Authentication을 직접 구현할 수 있습니다.

인증에 대한 이전 자습서here를 방문하십시오.

이제 시작하겠습니다.

1단계. 라라벨 2단계 구성 설정



ENV를 사용하여 다음 코드를 추가합니다.

AUTHY_KEY=YOUR API KEY HERE


Authy 애플리케이션을 만드는 것을 잊지 마십시오.

이 구성에서는 나중에 Authy Application API 키를 추가해야 합니다.

그런 다음 완료되면 다음 배열 값을 config/services.php 에 추가하십시오.

'authy' => [
   'key' => env('AUTHY_KEY')
]


2단계: 작성기를 통해 인증 설치


composer.json 내부에 JSON 값이 필요하면 다음 줄을 추가합니다.

"authy/php": "^4.0"


다음과 같아야 합니다.

"require": {
        "php": "^8.0.2",
        "guzzlehttp/guzzle": "^7.2",
        "laravel/framework": "^9.2",
        "laravel/sanctum": "^2.14.1",
        "laravel/tinker": "^2.7",
        "authy/php": "^4.0"
},


그런 다음 아래의 다음 명령을 실행합니다.

composer update


3단계: Authy Two Factor 열을 사용자 테이블에 추가



다음 명령을 실행합니다.

php artisan make:migration add_authy_columns_to_users_table


다음은 마이그레이션의 최종 코드입니다.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->boolean('authy_status')->nullable()->after('password');
            $table->string('authy_id', 25)->after('authy_status');
            $table->string('authy_country_code', 10)->after('authy_id');
            $table->string('authy_phone')->after('authy_country_code');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('authy_status');
            $table->dropColumn('authy_id', 25);
            $table->dropColumn('authy_country_code', 10);
            $table->dropColumn('authy_phone');
        });
    }
};


그럼 일단 완료. 다음 명령을 실행하십시오.

php artisan migrate


4단계: 사용자 모델 채울 수 있는 값 설정 및 2단계 확인



아래는 최종 코드입니다.

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'username',
        'password',
        'authy_status',
        'authy_id',
        'authy_country_code',
        'authy_phone'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
        'authy_id'
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * Always encrypt password when it is updated.
     *
     * @param $value
     * @return string
     */
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = bcrypt($value);
    }

    /**
     * Check if factor enabled
     * 
     * @return boolean
     */
    public function isTwoFactorEnabled()
    {
        return $this->authy_status == 1 ? true : false;
    }
}


5단계: Authy에 대한 서비스 클래스 추가



이제 Authy.php 탐색App/Services 폴더에 대한 서비스 클래스를 추가한 다음 TwoFactor 폴더를 추가하겠습니다. 완료되면 Authy.php 파일 생성 후 다음 코드를 추가합니다.

<?php

namespace App\Services\TwoFactor;

class Authy {

    /**
     * @var \Authy\AuthyApi
     */
    private $api;

    public function __construct()
    {
        $this->api = new \Authy\AuthyApi(config('services.authy.key'));
    }

    /**
     * @param $email
     * @param $phoneNumber
     * @param $countryCode
     * @return int
     * @throws \Exception
     */
    function register($email, $phoneNumber, $countryCode)
    {
        $user = $this->api->registerUser($email, $phoneNumber, $countryCode);

        return $user;
    }

    /**
     * @param $authyId
     * @return bool
     * @throws \Exception
     */
    public function sendToken($authyId)
    {
        $response = $this->api->requestSms($authyId);

        return $response;
    }

    /**
     * @param $authyId
     * @param $token
     * @return bool
     * @throws \Exception Nothing will be thrown here
     */
    public function verifyToken($authyId, $token)
    {
        $response = $this->api->verifyToken($authyId, $token);

        return $response;
    }

    /**
     * @param $authyId
     * @return \Authy\value status
     * @throws \Exception if request to api fails
     */
    public function verifyUserStatus($authyId) {
        $response = $this->api->userStatus($authyId);

        return $response;
    }

}


6단계: 프로필 컨트롤러 및 경로 설정



이제 다음 명령을 실행하여 ProfileController를 생성해 보겠습니다.

php artisan make:controller ProfileController




그런 다음 아래에 다음 코드를 추가합니다.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use App\Services\TwoFactor\Authy;

class ProfileController extends Controller
{
    protected $users;
    protected $authy;

    public function __construct(User $users, Authy $authy) 
    {
        $this->users = $users;
        $this->authy = $authy;
    }

    public function index() 
    {
        return view('profile.index');
    }

    public function enableTwoFactor(Request $request) 
    {
        $user = auth()->user();

        $checkUser = User::where('authy_country_code', $request->get('country_code'))
            ->where('authy_phone', $request->get('phone_number'))
            ->first();

        if(is_null($checkUser)) {
            $register = $this->authy->register(
                $user->email, 
                $request->get('phone_number'),
                $request->get('country_code')
            );

            if ($register->ok()) {
                $authyId = $register->id();

                $user->update([
                    'authy_status' => false,
                    'authy_id' => $authyId,
                    'authy_country_code' => $request->get('country_code'),
                    'authy_phone' => $request->get('phone_number')
                ]);
            } else {
                return redirect('profile')->with('authy_errors', $register->errors());
            }

        } else {
            $authyId = $checkUser->authy_id;
        }

        $this->authy->sendToken($authyId);

        return redirect('profile/two-factor/verification');
    }

    public function disableTwoFactor(Request $request) 
    {
        $user = auth()->user();

        $user->update([
            'authy_status' => false
        ]);

        return redirect('profile')
            ->with('success',  __('Two factor authentication has been disabled.'));
    }

    public function getVerifyTwoFactor() 
    {
        return view('profile.verify-two-factor');
    }

    public function postVerifyTwoFactor(Request $request) 
    {
        $user = auth()->user();

        $verfiy = $this->authy->verifyToken($user->authy_id, $request->get('authy_token'));

        if ( $verfiy->ok() ) {
            $user->update(['authy_status' => 1]);

            return redirect('profile')
                ->with('success', __('Two factor authentication has been enabled.'));
        }

        return redirect('profile/two-factor/verification')
            ->with('errors', __('Invalid token. Please try again.'));
    }
}


ProfileController 기능은 2요소 활성화, 2요소 비활성화, 추가 시 2요소 확인으로 구성됩니다.





그런 다음 프로필 경로를 설정해 보겠습니다.

Route::group(['middleware' => ['auth']], function() {

    /**
     * Profile Routes
     */
    Route::get('/profile', 'ProfileController@index')
        ->name('profile.index');
    Route::post('/profile/two-factor/enable', 'ProfileController@enableTwoFactor')
        ->name('profile.enableTwoFactor');
    Route::post('/profile/two-factor/disable', 'ProfileController@disableTwoFactor')
        ->name('profile.disableTwoFactor');
    Route::get('/profile/two-factor/verification', 'ProfileController@getVerifyTwoFactor')
        ->name('profile.getVerifyTwoFactor');
    Route::post('/profile/two-factor/verification', 'ProfileController@postVerifyTwoFactor')
        ->name('profile.postVerifyTwoFactor');
});


이제 프로필에 대한 탐색을 설정한 다음 탐색해 보겠습니다resources/views/layouts/partials/navbar.blade.php. 아래 코드를 참조하십시오.

<header class="p-3 bg-dark text-white">
  <div class="container">
    <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
      <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
        <svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap"><use xlink:href="#bootstrap"/></svg>
      </a>

      <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
        <li><a href="#" class="nav-link px-2 text-secondary">Home</a></li>
        <li><a href="#" class="nav-link px-2 text-white">Features</a></li>
        <li><a href="#" class="nav-link px-2 text-white">Pricing</a></li>
        <li><a href="#" class="nav-link px-2 text-white">FAQs</a></li>
        <li><a href="#" class="nav-link px-2 text-white">About</a></li>
      </ul>

      <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
        <input type="search" class="form-control form-control-dark" placeholder="Search..." aria-label="Search">
      </form>

      @auth

        <div class="dropdown">
          <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton2" data-bs-toggle="dropdown" aria-expanded="false">
            {{auth()->user()->name}}
          </button>
          <ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="dropdownMenuButton2">
            <li><a class="dropdown-item active" href="{{ route('profile.index') }}">Profile</a></li>
            <li><hr class="dropdown-divider"></li>
            <li><a class="dropdown-item" href="{{ route('logout.perform') }}">Logout</a></li>
          </ul>
        </div>
      @endauth

      @guest
        <div class="text-end">

          <a href="{{ route('login.perform') }}" class="btn btn-outline-light me-2">Login</a>
          <a href="{{ route('register.perform') }}" class="btn btn-warning">Sign-up</a>
        </div>
      @endguest
    </div>
  </div>
</header>


7단계: 인증에서 Authy Two Factor 구현



이제 인증에 Authy Two Factor를 구현해 보겠습니다. LoginController.php 내부의 인증 코드를 수정해야 합니다. 다음은 수정된 코드 내부authenticated() 메서드입니다.



<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\TwoFactor\Authy;
use App\Http\Requests\LoginRequest;
use Illuminate\Support\Facades\Auth;
use App\Services\Login\RememberMeExpiration;

class LoginController extends Controller
{
    use RememberMeExpiration;

    protected $authy;

    public function __construct(Authy $authy) 
    {
        $this->authy = $authy;
    }

    /**
     * Display login page.
     * 
     * @return Renderable
     */
    public function show()
    {
        return view('auth.login');
    }

    /**
     * Handle account login request
     * 
     * @param LoginRequest $request
     * 
     * @return \Illuminate\Http\Response
     */
    public function login(LoginRequest $request)
    {
        $credentials = $request->getCredentials();

        if(!Auth::validate($credentials)):
            return redirect()->to('login')
                ->withErrors(trans('auth.failed'));
        endif;

        $user = Auth::getProvider()->retrieveByCredentials($credentials);

        Auth::login($user, $request->get('remember'));

        if($request->get('remember')):
            $this->setRememberMeExpiration($user);
        endif;

        return $this->authenticated($request, $user);
    }

    /**
     * Handle response after user authenticated
     * 
     * @param Request $request
     * @param Auth $user
     * 
     * @return \Illuminate\Http\Response
     */
    protected function authenticated(Request $request, $user) 
    {
        if(!$user->isTwoFactorEnabled()){
            return redirect()->intended();
        }

        $status = $this->authy->verifyUserStatus($user->authy_id);

        if($status->ok() && $status->bodyvar('status')->registered) {
            Auth::logout();

            $request->session()->put('auth.2fa.id', $user->id);

            $sms = $this->authy->sendToken($user->authy_id);

            if($sms->ok()){
                return redirect('/token');
            }
        } else {
             Auth::logout();
            return redirect('login')->with('message', __('Could not confirm Authy status!'));
        }

    }
}


이제 추가 계층 인증을 위해 TwoFactorController를 생성해 보겠습니다. 다음 명령을 실행하여 생성합니다.

php artisan make:controller TwoFactorController


다음은 TwoFactorController의 전체 소스 코드입니다.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use App\Services\TwoFactor\Authy;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\TwoFactorVerifyRequest;

class TwoFactorController extends Controller
{
    public function __construct(User $users, Authy $authy) 
    {
        $this->users = $users;
        $this->authy = $authy;
    }

    /**
     * Display login page.
     * 
     * @return Renderable
     */
    public function show()
    {
        return view('auth.token');
    }

    public function perform(TwoFactorVerifyRequest $request) 
    {
        $user = $this->users->find(session('auth.2fa.id'));

        if(!$user){
            return redirect('login');
        }

        $verfiy = $this->authy->verifyToken($user->authy_id, $request->get('authy_token'));

        if($verfiy->ok()){
            Auth::login($user);
            return redirect('/');
        } else {
            return redirect('token')->with('authy_error', __('The token you entered is incorrect'));
        }
    }
}


Two Factor에 대한 유효성 검사 요청을 생성해 보겠습니다. 다음 명령을 실행하십시오.

php artisan make:request TwoFactorVerifyRequest


그런 다음 다음 코드를 추가합니다.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TwoFactorVerifyRequest 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 [
            'authy_token' => ['required', 'digits_between:6,10']
        ];
    }
}


그런 다음 Authy Two Factor를 확인하기 위한 보기를 만들어 보겠습니다. resources/views/auth 폴더 안에 token.blade.php 파일을 만듭니다. 그런 다음 다음 코드를 추가합니다.

@extends('layouts.auth-master')

@section('content')
    <form method="post" action="{{ route('token.perform') }}">

        <input type="hidden" name="_token" value="{{ csrf_token() }}" />
        <img class="mb-4" src="{!! url('images/bootstrap-logo.svg') !!}" alt="" width="72" height="57">

        <h1 class="h3 mb-3 fw-normal">Two Factor Authentication</h1>

        @if(Session::get('authy_error', false))
            <div class="alert alert-warning" role="alert">
                <i class="fa fa-check"></i>
                {{ Session::get('authy_error'); }}
            </div>
        @endif

        <div class="form-group form-floating mb-3">
            <input type="text" class="form-control" name="authy_token" value="{{ old('authy_token') }}" placeholder="Authy Token" required="required" autofocus>
            <label for="floatingName">Authy Token</label>
            @if ($errors->has('authy_token'))
                <span class="text-danger text-left">{{ $errors->first('authy_token') }}</span>
            @endif
        </div>

        <button class="w-100 btn btn-lg btn-primary" type="submit">Verify</button>

        @include('auth.partials.copy')
    </form>
@endsection


그런 다음 이중 요소 경로를 설정해 보겠습니다. 아래를 참조하십시오.

/**
 * Two Factor Routes
 */
Route::get('/token', 'TwoFactorController@show')->name('token.show');
Route::post('/token', 'TwoFactorController@perform')->name('token.perform');


다음은 경로의 전체 소스 코드입니다.

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::group(['namespace' => 'App\Http\Controllers'], function()
{   
    /**
     * Home Routes
     */
    Route::get('/', 'HomeController@index')->name('home.index');

    Route::group(['middleware' => ['guest']], function() {
        /**
         * Register Routes
         */
        Route::get('/register', 'RegisterController@show')->name('register.show');
        Route::post('/register', 'RegisterController@register')->name('register.perform');

        /**
         * Login Routes
         */
        Route::get('/login', 'LoginController@show')->name('login.show');
        Route::post('/login', 'LoginController@login')->name('login.perform');


        /**
         * Two Factor Routes
         */
        Route::get('/token', 'TwoFactorController@show')->name('token.show');
        Route::post('/token', 'TwoFactorController@perform')->name('token.perform');
    });

    Route::group(['middleware' => ['auth']], function() {

        /**
         * Profile Routes
         */
        Route::get('/profile', 'ProfileController@index')
            ->name('profile.index');
        Route::post('/profile/two-factor/enable', 'ProfileController@enableTwoFactor')
            ->name('profile.enableTwoFactor');
        Route::post('/profile/two-factor/disable', 'ProfileController@disableTwoFactor')
            ->name('profile.disableTwoFactor');
        Route::get('/profile/two-factor/verification', 'ProfileController@getVerifyTwoFactor')
            ->name('profile.getVerifyTwoFactor');
        Route::post('/profile/two-factor/verification', 'ProfileController@postVerifyTwoFactor')
            ->name('profile.postVerifyTwoFactor');

        /**
         * Logout Routes
         */
        Route::get('/logout', 'LogoutController@perform')->name('logout.perform');
    });
});


Laravel 9 Two Factor Authentication을 읽어주셔서 감사합니다. 이 튜토리얼이 도움이 되었으면 합니다. 이 코드를 다운로드하려면 여기https://codeanddeploy.com/blog/laravel/laravel-9-2fa-two-factor-authentication-with-authy를 방문하십시오.

행복한 코딩 :)

좋은 웹페이지 즐겨찾기