QR Login in PHP like whatsapp using ajax polling
Tags: laravel, qr, polling, qrlogin
Published: 2021-10-02 19:18:37

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
```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


```
in api.php
```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 
?>
```

[API doc](https://documenter.getpostman.com/view/6553325/UUy39SJv)

1. Lets generate the QR code
![Click on create with QR button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zxxe905fwejcqt7juujj.png)
look in the console, qr key, and it started polling for qrscan check
![Qr code generated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8oyi01st33ya0xy6g9do.png)

2. Scan it with the QR scanner or use the postman api to simulate the scan
api:
```/api/login/mobile/scan/qrcode?key=yY5v8Sz8h5nZB8ysnRKh3FGNwdK3Eb&type=1
```
pass the userpasscode in headers

![Scanned successfully](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/26rqz1vwh9t6mg5lqs5f.png)

3. When dologinapi hit is done

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

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

![Login successfull](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3p5c7sprf23ub8l5caua.png)

5. QR scanner look like this 
![QR scanner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt0l7o45fw4muoshs6l9.png)

here is the code [gist](https://gist.github.com/sahilkashyap64/b2d2db0999b3d540d2578da9a0f728c0)

here's the controller code
```php
<?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
  }
}
?>
```
blade files are present in the [gist](https://gist.github.com/sahilkashyap64/b2d2db0999b3d540d2578da9a0f728c0)
[Github code](https://github.com/sahilkashyap64/qrlogin-php)