DEV Community

Cover image for QR Login in PHP like whatsapp using ajax polling
Sahil kashyap
Sahil kashyap

Posted on • Edited on

QR Login in PHP like whatsapp using ajax polling

Cross mobile to web browser login
Method used: Polling
Things required: memcache and imagick extension(in php)

  1. Create a QR code using a random string,Save the random string in memcache for 3 minutes
  2. Poll did scan api
  3. Poll did login api
  4. QR scanner app

in web.php

//web.php
Route::get('/qrtesting2', 'Admin\QRLoginTwoController@qr'); //shows the QR cde on screen
Route::get('/qrscanner', 'Admin\QRLoginTwoController@qrscanner');// scan the qr code
Route::post('web/login/entry/login', 'Admin\QRLoginTwoController@loginEntry');//Check whether the login has been confirmed ,and return the token in response and redirect


Enter fullscreen mode Exit fullscreen mode

in api.php

//api.php

<?php

//api doc : https://documenter.getpostman.com/view/6553325/UUy39SJv
//article: https://dev.to/sahilkashyap64/qr-login-in-php-2pgf
Route::post('/login/create/qrcode', 'Admin\QRLoginTwoController@CreateQrcodeAction');//creates QR and save it as png 
Route::post('/login/mobile/scan/qrcode', 'Admin\QRLoginTwoController@mobileScanQrcodeAction');//gets scaned by phone with key passed in header
Route::post('/login/qrcodedoLogin', 'Admin\QRLoginTwoController@qrcodeDoLoginAction'); //this url is used when qr code is scanned successfully
Route::post('/login/scan/qrcode', 'Admin\QRLoginTwoController@isScanQrcodeAction'); //Check whether the code has been scanned 
?>
Enter fullscreen mode Exit fullscreen mode

API doc

  1. Lets generate the QR code
    Click on create with QR button
    look in the console, qr key, and it started polling for qrscan check
    Qr code generated

  2. Scan it with the QR scanner or use the postman api to simulate the scan
    api:

Enter fullscreen mode Exit fullscreen mode

pass the userpasscode in headers

Scanned successfully

  1. When dologinapi hit is done

api: api/login/qrcodedoLogin?key=3pdBjf5tVq0onKbQQJBokrTxhC2z2r&type=scan&login=12345pass&sign=p3p3zRWd72mZ

  1. Login will be succsfully, you'll get the passcode you passed

Login successfull

  1. QR scanner look like this QR scanner

here is the code gist

here's the controller code

<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cache;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use Memcache;
use Illuminate\Support\Facades\Auth;
class QRLoginTwoController extends Controller
{
    // QR code should be only avilable for non logged in user
    public function qr()
    {

        if (Auth::check())
        {

            return "you are already logged in, open it in incognito mode";

        }
        else
        {

            return view('frontend.drm.qr');
        }
    }

    //QR scanner only avilable for logged in user
    public function qrscanner()
    {
        if (Auth::check())
        {
            $login = true;

            return view('frontend.drm.qrscanner', compact('login'));
        }
        return redirect()->route('home');
    }

    public function CreateQrcodeAction()
    {

        $url = 'http://' . $_SERVER['HTTP_HOST']; // Get the current url
        // dd($url);
        $http = $url . '/api/login/mobile/scan/qrcode'; // Verify the url method of scanning code
        $key = Str::random(30); //$this->getRandom(30); // The key value stored in memcache, a random 32-bit string
        $random = mt_rand(1000000000, 9999999999); //random integer
        $_SESSION['qrcode_name'] = $key; // Save the key as the name of the picture in the session
        $forhash = substr($random, 0, 2);
        $sgin_data = HashUserID($forhash); // The basic algorithm for generating the sign string
        $sgin = strrev(substr($key, 0, 2)) . $sgin_data; // Intercept the first two digits and reverse them
        $value = $http . '?key=' . $key . '&type=1'; // Two-dimensional Code content
        $pngImage = QrCode::format('png')
        //  ->merge(public_path('frontend/img/streamly-logo.png'), 0.3, true)
        ->size(300)
            ->errorCorrection('H')
            ->generate($value, public_path('assets/img/qrcodeimg/' . $key . '.png'));

        $return = array(
            'status' => 0,
            'msg' => ''
        );

        $qr = public_path('assets/img/qrcodeimg/' . $key . '.png');

        if (!file_exists($qr))
        {
            $return = array(
                'status' => 0,
                'msg' => ''
            );
            return response()->json($return, 404);
            //   return "no found qr img";

        }
        $qr = asset('assets/img/qrcodeimg/' . $key . '.png');

        $mem = new \Memcache();
        $mem->connect('127.0.0.1', 11211);
        $res = json_encode(array(
            'sign' => $sgin,
            'type' => 0
        ));
        // store in memcache, expiration time is three minutes
        $mem->set($key, $res, 0, 180); // 180
        $return = array(
            'status' => 1,
            'msg' => $qr,
            'key' => $key
        );
        return response()->json($return, 200);

    }

    // Mobile device scan code
    public function mobileScanQrcodeAction(Request $request)
    {
        $key = $_GET['key'];
        $url = 'http://' . $_SERVER['HTTP_HOST'];
        $agent = $_SERVER["HTTP_USER_AGENT"];
        $headerpasscode = $request->header('userpasscode');

        $http = $url . '/api/login/qrcodedoLogin'; // Return to confirm the login link
        $mem = new \Memcache();
        $mem->connect('127.0.0.1', 11211);
        $data = json_decode($mem->get($key) , true);

        if (empty($data))
        {
            $return = array(
                'status' => 2,
                'msg' => 'expired'
            );
            return response()->json($return, 200);
        }
        $data['type'] = 1; // Increase the type value to determine whether the code has been scanned
        $res = json_encode($data);
        $mem->set($key, $res, 0, 180);
        $http = $http . '?key=' . $key . '&type=scan&login=' . $headerpasscode . '&sign=' . $data['sign'];
        $return = array(
            'status' => 1,
            'msg' => $http
        );
        return response()->json($return, 200);
    }
    /* *
     * Log in after the client scans the code
     * $sign passes the identification when the client scans the code, and compares it with the memcache
     * $key is the key passed in the QR code on the web page
     * $uid The user id sent by scanning the code after the client logs in
     * @return void
    */
    public function qrcodeDoLoginAction(Request $request)
    {

        $login = $_GET['login']; //jwt or passcode
        $key = $_GET['key'];
        $sign = $_GET['sign'];
        $mem = new \Memcache();
        $mem->connect('127.0.0.1', 11211);
        $data = json_decode($mem->get($key) , true); // Remove the value of memcache
        if (empty($data))
        {
            $return = array(
                'status' => 2,
                'msg' => 'expired'
            );
            return response()->json($return, 200);
        }
        else
        {
            if (!isset($data['sign']))
            {
                $return = array(
                    'status' => 0,
                    'msg' => 'Sign notset'
                );
            }
            if ($data['sign'] != $sign)
            { // Verify delivery Sign
                $return = array(
                    'status' => 0,
                    'msg' => 'Verification Error'
                );
                //  return  $this ->createJsonResponse( $return );
                return response()->json($return, 403);
            }
            else
            {
                if ($login)
                { // Mobile phone scan code webpage login, save the user name in memcache
                    $data['jwt'] = $login;
                    $res = json_encode($data);
                    $mem->set($key, $res, 0, 180);
                    $return = array(
                        'status' => 1,
                        'msg' => 'Login successful'
                    );
                    //  return  $this ->createJsonResponse( $return );
                    return response()->json($return, 200);
                }
                else
                {
                    $return = array(
                        'status' => 0,
                        'msg' => 'Please pass the correct user information'
                    );
                    //  return  $this ->createJsonResponse( $return );
                    return response()->json($return, 401);
                }
            }
        }

    }

    /* *
     * Check whether the code has been scanned, this checked by polling,
     * it looks for the key, the filename,
    */
    public function isScanQrcodeAction(Request $request)
    {

        $key = $request['key'];
        $mem = new \Memcache();
        $mem->connect('127.0.0.1', 11211);
        $data = json_decode($mem->get($key) , true);
        if (empty($data))
        {
            $return = array(
                'status' => 2,
                'msg' => 'expired'
            );
        }
        else
        {
            if ($data['type'])
            {
                $return = array(
                    'status' => 1,
                    'msg' => 'success'
                );

            }
            else
            {
                $return = array(
                    'status' => 0,
                    'msg' => ''
                );
            }
        }
        return response()->json($return, 200);
        // return  $this->createJsonResponse( $return );

    }

    public function loginEntry(Request $request)
  {

     $key=$request['key'];
     if (empty($key)){

     $return = array ('status'=>2,'msg'=>'key not provided' );
     return response()->json($return, 200);
     }
    $mem = new \Memcache();
    $mem->connect('127.0.0.1',11211);
    $data = json_decode($mem->get($key),true); 
//     $passcode=$data['login'];
    if (empty($data)){
     $return = array ('status'=>2,'msg'=>'expired' );
     return response()->json($return, 200);
 } else {
     if (isset($data['jwt'])){
      $userid=UnHashUserID($data['jwt']);
      $user = Auth::loginUsingId($userid, true);


         $return = array ('status'=>1,'msg'=>'success','jwt'=>$data['jwt'],'user'=>$user );
         return response()->json($return, 200);
    } else {
         $return = array ('status'=>0,'msg'=>'','data'=>$data );
         return response()->json($return, 400);
    }
}
    // Use passcode to login and return the object in reposne or jWT
    // extract memcache and the login var jwt/passcode 
    //use passcode to login and return the jwt
  }
}
?>
Enter fullscreen mode Exit fullscreen mode

blade files are present in the gist
Github code

Top comments (0)