27
loading...
This website collects cookies to deliver better user experience
Through this guide, I highlighted some issues, their solutions and modifications to help have a better experience with this packages.
An advantage of using Google Authenticator is that after downloading the app to your smartphone, you can use it offline unlike other 2FA options which need to be connected to the internet to work which may be a disadvantage to users in a cut off location *for example, the basement of a building.
After the user logs in successfully, a prompt showing a QR code and alternatively a code(set of characters to manually input if the user may not be able to scan QR code).
Upon scanning or submitting the code, the server generates a secret key which is passed to the user.
The secret key is combined with the current Unix timestamp to generate a six-digit code using a message authentication code (HMAC) based algorithm.
The six-digit code is the OTP and it changes every 30 seconds.
laravel new project_name
or
composer create-project laravel/laravel project_name
composer require laravel/ui
Artisan
command:php artisan ui bootstrap --auth
npm install
npm run dev
Artisan Command
:php artisan serve
http://localhost:8000/register
You should be able to view the register and login page like this:
P.S: We haven't run the migrations yet so submitting the forms will return an error message.
P.S: The QR code is accessible ONLY once for maximum security and if the user needs to set up 2FA again, they will have to repeat the process and invalidate the old one.
composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code
php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider"
RegisterController
with register()
method.P.S: Include the Request
class outside the controller class.
use Illuminate\Http\Request;
public function register(Request $request)
{
//Validate the incoming request using the already included validator method
$this->validator($request->all())->validate();
// Initialise the 2FA class
$google2fa = app('pragmarx.google2fa');
// Save the registration data in an array
$registration_data = $request->all();
// Add the secret key to the registration data
$registration_data["google2fa_secret"] = $google2fa->generateSecretKey();
// Save the registration data to the user session for just the next request
$request->session()->flash('registration_data', $registration_data);
// Generate the QR image. This is the image the user will scan with their app
// to set up two factor authentication
$QR_Image = $google2fa->getQRCodeInline(
config('app.name'),
$registration_data['email'],
$registration_data['google2fa_secret']
);
// Pass the QR barcode image to our view
return view('google2fa.register', ['QR_Image' => $QR_Image, 'secret' => $registration_data['google2fa_secret']]);
}
register()
trait.register()
method, we need to update the trait to avoid a clash with the default register()
method from the authentication scaffold.use RegistersUsers;
use RegistersUsers {
// We are doing this so the predefined register method does not clash with the one we just defined.
register as registration;
}
register()
method already redirects to view(google2fa.register.blade.php)
. This means we will create agoogle2fa
folder and a register.blade.php
file in it. The full path will be resources/views/google2fa/register.blade.php
.@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Set up Google Authenticator</div>
<div class="panel-body" style="text-align: center;">
<p>Set up your two factor authentication by scanning the barcode below. Alternatively, you can use the code {{ $secret }}</p>
<div>
<img src="{{ $QR_Image }}">
</div>
<p>You must set up your Google Authenticator app before continuing. You will be unable to login otherwise</p>
<div>
<a href="/complete-registration"><button class="btn-primary">Complete Registration</button></a>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
P.S: Now after submitting valid credentials at registration, the user is redirected to a page with a valid QR code and also the secret(if they cant scan the code). However, users can't complete registration yet because we are yet to set up the controllers and route to handle that.
up()
method in database/migrations/2014_10_12_000000_create_users_table.php
like this:public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->text('google2fa_secret');
$table->timestamps();
});
}
Artisan
command:php artisan run migrate
P.S: If you have run your migrations before now, Here is a guide I wrote about Adding and Removing columns from existing tables in database . (Fear not! I got you covered😎)
RegisterController
with completeRegistration()
method.public function completeRegistration(Request $request)
{
// add the session data back to the request input
$request->merge(session('registration_data'));
// Call the default laravel authentication
return $this->registration($request);
}
User
model and create()
method in RegisterController
with google2fa_secret
field.User
model as hidden
and fillable
property so that it can be included and also hidden if we cast it to an array or JSON.
protected $fillable = [
'name',
'email',
'password',
'google2fa_secret'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
'google2fa_secret'
];
create()
method to accept the field like this:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'google2fa_secret' => $data['google2fa_secret'],
]);
}
complete-registeration
.routes/web.php
file like this:Route::get('/complete-registration', [App\Http\Controllers\Auth\RegisterController::class, 'completeRegistration'])->name('complete-registration');
google2fa_secret
.google2fa_secret
in the User
model like this:public function setGoogle2faSecretAttribute($value)
{
$this->attributes['google2fa_secret'] = encrypt($value);
}
public function getGoogle2faSecretAttribute($value)
{
return decrypt($value);
}
Now a user that successfully registers with the correct OTP should see this:
composer require bacon/bacon-qr-code:~1.0.3
pragmarx/google2fa-laravel
package provides a middleware to prevent users from accessing the app unless OTP has been provided.routeMiddleware
array in app/Http/Kernel.php
before we can use it.protected $routeMiddleware = [
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
];
resources/views/google2fa/index.blade.php
prompting for the OTP until a valid OTP is sent.After scanning the QR code or inputting Secret on the Google Authenticator app, it automatically generates an OTP on the Authenticator App.
Click on Complete Registration then the user is prompted for the OTP and if OTP submitted is valid(being careful that the OTP on the app refreshes every 30 seconds, so the user must input the current OTP) then, the user is redirected to home page else user will keep being prompted for the OTP until the submission is valid.
resources/views/google2fa/index.blade.php
looks like this:resources/views/google2fa/index.blade.php
like this:@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center align-items-center " style="height: 70vh;S">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading font-weight-bold">Register</div>
<hr>
@if($errors->any())
<b style="color: red">{{$errors->first()}}</b>
@endif
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('2fa') }}">
{{ csrf_field() }}
<div class="form-group">
<p>Please enter the <strong>OTP</strong> generated on your Authenticator App. <br> Ensure you submit the current one because it refreshes every 30 seconds.</p>
<label for="one_time_password" class="col-md-4 control-label">One Time Password</label>
<div class="col-md-6">
<input id="one_time_password" type="number" class="form-control" name="one_time_password" required autofocus>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Login
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
27