Skip to content
All posts
LaravelSecurity

Laravel 2FA Login with Microsoft Authenticator App

January 25, 2021·Read on Medium·

Two Factor Authentication (2FA) is an extra layer of protection used to ensure the security of online accounts beyond just a username and password. This 2FA already implemented in most modern website as extra layer of security. This 2FA basically requires you to verify your identity using a randomized 6-digit code.

Why Microsoft Authenticator App? Because its free! 😂

BTW, Adding 2FA in Laravel is quite simple actually. Make sure you install Microsoft Authenticator App in your device. This 2FA already in Fortify package and Jetstream Laravel. But i would like to create it myself so i have full control of it. 😜

Note that, this tutorial is just snippet of codes. Any UI or extra code need to create by yourself. This article intended to show how the 2FA can be used. This article is for intermediate developer

Install composer package

# composer require pragmarx/google2fa //required
# composer require endroid/qr-code // can change to your prefered QR package

Create a class, let’s call it as TwoFactorAuthenticationProvider.

TwoFactorAuthenticationProvider.php View on GitHub
<?php

class TwoFactorAuthenticationProvider
{
    protected $engine;

    public function __construct(Google2FA $engine)
    {
        $this->engine = $engine;
    }

    public function generateSecretKey()
    {
        return $this->engine->generateSecretKey();
    }

    public function generateRecoveryCodes($times = 8, $random = 10)
    {
        return Collection::times($times, function () {
            return Str::random($random).'-'.Str::random($random);
        })->toArray();
    }

    public function qrCodeUrl(string $companyName, string $companyEmail, string $secret)
    {
        return $this->engine->getQRCodeUrl($companyName, $companyEmail, $secret);
    }
    
    public function verify(string $secret, string $code)
    {
        return $this->engine->verifyKey($secret, $code);
    }
}

Let me explain,

public function generateSecretKey()
{
return $this->engine->generateSecretKey();
}

This function will generate a secret for each initialisation of 2FA. Note that, this secret key need to store securely in your database or anywhere you preferred bind with each user.

public function generateRecoveryCodes($times = 8, $random = 10)
{
return Collection::times($times, function () {
return Str::random($random).'-'.Str::random($random);
})->toArray();
}

This function used for account recovery. Its will generate 8 of codes with random characters. This is important where if you can’t use your 2FA, recovery codes come to the rescue. Note that, this recovery codes also need to be stored in database for recovery purpose.

public function verify(string $secret, string $code)
{
return $this->engine->verifyKey($secret, $code);
}

This function use to verify your 6 digit input from 2FA page with your user secret key. It will thrown IncompatibleWithGoogleAuthenticatorException if doesn’t match.

Usage

Initial

Use created class earlier at Controller or any where you want to use

$provider = app(TwoFactorAuthenticationProvider::class);
or
function __construct(TwoFactorAuthenticationProvider $provider) {}

Create a secret key for current user and encrypt it.

$code = $provider->generateSecretKey();
$encrypted_code = encrypt($code);

Don’t forget to create the list of recovery codes

$recovery_codes = $provider->generateRecoveryCodes();

and store it based on user, for example

foreach($recovery_codes as $recovery_code) {
RecoveryCodeModel::create([
'user_id' => $user->id,
'code' => encrypt($recovery_code)
]);
} or RecoveryCodeModel::create([
'user_id' => $user->id,
'code' => encrypt(json_encode($recovery_codes))
]);

Then, you must generate a QR Code to register in Microsoft Authenticator App.

$strAuthUrl = $provider->qrCodeUrl(
YOUR ACCOUNT NAME,
YOUR USERNAME,
$code // Secret Key
);

The provider will generate long authentication string and convert the string into QR Code images

For example, i’m using Endroid QrCode

$qrCode = new QrCode($strAuthUrl);
$qrCode->setMargin(10);
$qrCode->setSize(600);
$qrCode->setWriterByName('png');
$qrCode->setEncoding('UTF-8');
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH());
$qrCode->setLogoWidth(150);
$qrCode->setRoundBlockSize(true);
$qrCode->setValidateResult(false);

$dataURI = $qrCode->writeString();

From the URI, store it in your storage

$url = Storage::put('qrcode.png', $dataURI);

You’ll get the public URL for your QR Code. Take the secret key and the URL and save it in the Database. Let’s for example,

$user->update([
'twofa_key' => $encrypted_code,
'twofa_qr' => $url
]);

Once you display at your user end, it will be something like this.

How about verification

Lets create the Validation Request class first and called TwoFactorLoginRequest.

TwoFactorLoginRequest.php View on GitHub
<?php

class TwoFactorLoginRequest extends FormRequest
{
    protected $challengedUser;

    public function authorize()
    {
        return auth()->user();
    }

    public function rules()
    {
        return [
            'secret' => 'nullable|string',
            'recovery_code' => 'nullable|string',
        ];
    }
  
    public function hasValidCode()
    {
        return $this->secret && app(TwoFactorAuthenticationProvider::class)->verify(
                decrypt($this->challengedUser()->twofa_key), $this->secret
            );
    }

    public function validRecoveryCode()
    {
        if (! $this->recovery_code) {
            return;
        }

        return $this->challengedUser()->recoveryCodes()->where('used', false)->get()->filter(function ($code) {
            return decrypt($code->code) == $this->recovery_code;
        })->first();
    }

    public function challengedUser()
    {
        if ($this->challengedUser) {
            return $this->challengedUser;
        }

        return $this->challengedUser = auth()->user();
    }
}

Its just normal FormRequest class but added 2 more function hasValidCode() and validRecoveryCode()

In your authentication controller, for example LoginController, you can create new function called verify2fa and verifyRecovery2fa

public function verify2fa(TwoFactorLoginRequest $request)
{
if (!$request->hasValidCode()) {
return back()->withErrors('Wrong Code');
}

return redirect()->intended($this->redirectPath());
} public function verifyRecovery2fa(TwoFactorLoginRequest $request)
{
if (!$code = $request->validRecoveryCode()) {
return back()->withErrors('Wrong Recovery Code');
}

return redirect()->intended($this->redirectPath());
}

Here some UI i created to authenticated to above method.

After user login with email and password. This page will appear first.

BUT First, you need to scan QrCode first. If not, you need recovery code to login

After user “use a recovery code”, this page will appear.

You have to save the 8 random codes somewhere else so that you can use in this page.

OR else, delete the user and create again. 😅

Conclusion

This article intended to show how the 2FA can be used. You may refer How to Add Google’s Two Factor Authentication to Laravel ― Scotch.io, on how to create an UI if you are a beginneer

References

Found this helpful?

If this article saved you time or solved a problem, consider supporting — it helps keep the writing going.

Originally published on Medium.

View on Medium