QR login like whatsapp using webocket php
Tags: laravel, websocket, qrcode, whatsapp
Published: 2021-10-21 21:23:47


QR login using websocket in laravel
- QR test page in incognito mode
![QR test page in incognito mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rij5wf6rjvlsqjk776uc.png)


- When QR scanned
![When QR scanned successfully](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w95287tsynim2cbmkydd.png)

- Things happening in the websocket
![WS console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34ejq6ts0915g5tvlwvy.png)


package used:
1. cboden/ratchet


Our app and websocket are on different port.
Let's setup command to run the websocket

```php
<?php
namespace App\Console\Commands;

use Illuminate\Console\Command;

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\Http\Controllers\WebSocketController;
use React\EventLoop\Factory;
use React\Socket\SecureServer;
use React\Socket\Server;
class WebSocketServer extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'websocket:init';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Initializing Websocket server to receive and manage connections';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    { //for local
        // $this->forlocal();
        

        //for prod server
        $this->forprodserver();

    }

    public function forlocal()
    {
        $server = IoServer::factory(new HttpServer(new WsServer(new WebSocketController())) , 8090);
        $server->run();
    }

    public function forprodserver()
    {
        $loop = Factory::create();
        $webSock = new SecureServer(new Server('0.0.0.0:8090', $loop) , $loop, array(
            'local_cert' => '/etc/letsencrypt/live/test.tv.com/fullchain.pem', // path to your cert
            'local_pk' => '/etc/letsencrypt/live/test.tv.com/privkey.pem', // path to your server private key
            'allow_self_signed' => true, // Allow self signed certs (should be false in production)
            'verify_peer' => false
        ));
        // Ratchet magic
        $webServer = new IoServer(new HttpServer(new WsServer(new WebSocketController())) , $webSock);
        $loop->run();
    }
}

```


Let's setup the  routes
web.php

```
<?php
Route::get('/qrtesting', 'Admin\QRLoginTwoController@qrtesting');
Route::post('web/loginws', 'Admin\QRLoginTwoController@loginWS');
Route::get('/qrscanner', 'Admin\QRLoginTwoController@qrscanner2');
```

Controller

```php
<?php
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class QRLoginTwoController extends Controller
{
    public function qrtesting()
    {

        return view('frontend.qrtesting');

    }
    public function qrscanner2()
    {
        if (Auth::check())
        {
            $login = true;

            return view('frontend.qrscanner2', compact('login'));
        }
        return redirect()->route('home');
    }
    public function loginWS(Request $request)
    {
        $key = $request['key'];
        if (empty($key))
        {

            $return = array(
                'status' => 2,
                'msg' => 'key not provided'
            );
            return response()->json($return, 200);
        }

        $userid = UnHashUserID($key);
        try
        {
            $user = Auth::loginUsingId($userid, true);
            $return = array(
                'status' => 1,
                'msg' => 'success',
                'jwt' => 1,
                'user' => $user
            );
            return response()->json($return, 200);
        }
        catch(Exception $exception)
        {

            return response()->json(['status' => 2, 'success' => false, 'message' => 'Some Error occured', 'error' => $exception->getMessage() , 'response_code' => 200,

            ], 200);
        }

    }

}
?>
```

WebSocketController.php
```php
<?php
namespace App\Http\Controllers;

use Illuminate\Support\Str;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class WebSocketController extends Controller implements MessageComponentInterface{
    private $connections = [];
    
    private $clients;
    private $cache;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage();
        // memory cache
       $this->cache = array();
    }
    public function multicast($msg) {
        foreach ($this->clients as $client) $client->send($msg);
    }

    public function send_to($to,$msg) {
        if (array_key_exists($to, $this->clientids)) $this->clientids[$to]->send($msg);
    }
     /**
     * When a new connection is opened it will be passed to this method
     * @param  ConnectionInterface $conn The socket/connection that just connected to your application
     * @throws \Exception
     */
    function onOpen(ConnectionInterface $conn){
        $this->clients->attach($conn);

        echo "New connection! ({$conn->resourceId})\n";
  }
    
     /**
     * This is called before or after a socket is closed (depends on how it's closed).  SendMessage to $conn will not result in an error if it has already been closed.
     * @param  ConnectionInterface $conn The socket/connection that is closing/closed
     * @throws \Exception
     */
    function onClose(ConnectionInterface $conn){
        unset($this->cache[$conn->resourceId]);
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
        $this->clients->detach($conn);
    }
    
     /**
     * If there is an error with one of the sockets, or somewhere in the application where an Exception is thrown,
     * the Exception is sent back down the stack, handled by the Server and bubbled back up the application through this method
     * @param  ConnectionInterface $conn
     * @param  \Exception $e
     * @throws \Exception
     */
    function onError(ConnectionInterface $conn, \Exception $e){
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }
    
     /**
     * Triggered when a client sends data through the socket
     * @param  \Ratchet\ConnectionInterface $conn The socket/connection that sent the message to your application
     * @param  string $msg The message received
     * @throws \Exception
     */
    function onMessage(ConnectionInterface $from, $msg){
        $numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
            $obj = json_decode($msg);

$type = $obj->type;
if($type=='client'){
    switch ($obj->step) {
        case 0:
            // echo "\n inside client,step0 \n";
            $token = $obj->token;
            $theuuid = UnHashUserID($token);
            
            //todo add jwt with 2minutes of token
            $tokenexist=array_key_exists($theuuid, $this->cache);
            
            if($tokenexist){
                echo "\n token exist ya \n";
                $ee=$this->cache[$theuuid]; 
                // print_r($ee);
                if($ee['status']=='0'){
                    $this->cache[$theuuid]['status'] = 1;
                    $this->cache[$theuuid] += ['child' => $from];
                    $myArray2[] = (object) ['step' => 1];
                    $Scan = new \SplObjectStorage();
            $Scan->code=0;
            $Scan->data=$myArray2[0];
            $Scan->msg="Scan code successfully";
            $this->cache[$theuuid]['parent']->send(json_encode($Scan));

            $ready2 = new \SplObjectStorage();
            $ready2->code=0;
            $ready2->data=$myArray2[0];
            $ready2->msg="Ready";
            $from->send(json_encode($ready2));
                };
            }else{

                echo "token doesn't exsit";
            }
            break;
            case 1:
                $myArray3[] = (object) ['step' => 2];
                $myArray4[] = (object) ['step' => 2,'username'=>$obj->username];
                foreach ($this->cache as $v) {
                    if($v['child']==$from){
                        // $token updateSessionToken;

            $ready3 = new \SplObjectStorage();
            $ready3->code=0;
            $ready3->data=$myArray4[0];
            $ready3->msg="Already logged in";
            if(array_key_exists("parent", $v)){}
                    $v['parent']->send(json_encode($ready3));
                    }
                }

            $ready = new \SplObjectStorage();
            $ready->code=0;
            $ready->data=$myArray3[0];
            $ready->msg="Login successful";
            $from->send(json_encode($ready));
    }
}else if($type=='server'){
    // echo "hello inside server";
    //to get the QR logo
    switch ($obj->step) {
        case 0:
            $uuid = $from->resourceId;//Str::random(30);
             echo $uuid;
            $token = HashUserID($uuid);
            // echo $token;
            $this->cache[$uuid] = [ 'status'=> 0, 'parent'=> $from ];
            $url = url(''); // Get the current url 
            // dd($url);
           $http = $url .'?t='.$token; // Verify the url method of scanning code 
           
            $myArray[] = (object) ['step' => 0,'url' => $http];
            $ready = new \SplObjectStorage();
            $ready->code=0;
            $ready->data=$myArray[0];
            $ready->msg="Ready";
            $from->send(json_encode($ready));
            
            break;
            
            
    }
}
    }

}
```





Let's generate the QR code:qrtesting.blade.php

```html
<!DOCTYPE HTML>
<html>

<head>

	
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script src="../frontend/qr/jquery.qrcode-0.11.0.min.js" ></script>
    <script type="text/javascript">
        $(document).ready(function() {
            initiate();

        });

        function initiate() {
            if ("WebSocket" in window) {
                var base = window.location.hostname;
               // var ws = new WebSocket('wss://'+base+':8090');
                var ws = new WebSocket('wss://'+base+':8090');
                console.log(ws);
                ws.onopen = function() {
                   ws.send(JSON.stringify({ type: "server", code: 0, step: 0 }));

                };
                ws.onmessage = function(evt) {
                   const data = JSON.parse(event.data);
                   //console.log("datafromservver",data);
                   const step = data.data && data.data.step;
                           if (step === 0) {
 //Generate QR Code and show to user.
                        $("#qrcode").qrcode({
                            "width": 100,
                            "height": 100,
                            "text": data.data.url
                        });
                          console.log("QR code generated successfully");
          
        }  else if (step === 2) {
            const { username, token } = data.data;
            //localStorage.setItem(TOKEN_KEY, token);

           $("#qrcode").html("");
            ws.close();
//alert(username);
is_loginfun(username);
        }            
					


                };


                ws.onclose = function() {
                    console.log("Connection is closed...");
                };
            } else {
                alert("WebSocket NOT supported by your Browser!");
            }
        }

	                
// Check whether the login has been confirmed 
function is_loginfun(param){
      var key = param;
      console.log("is_login called");
    $.ajax({
        type: "POST" ,
        dataType: "json" ,
        url: "web/loginws" ,
        data:{
            key:key ,
              "_token":"<?php echo  csrf_token() ?>"
        },
          
            headers: {'x-csrf-token': '<?php echo  csrf_token() ?>'}, 
        success:function(data) {
              if (data.status==1 ){
                  var uid = data.jwt;
                  var user = data.user;
                  console.log("user",user);
               
                console.log("login successfull",uid);

                alert("login successfull",uid);
                  window.location.href = '/';

            } else  if (data.status==2 ){
                alert(data.msg);
            }
        }
    });
}
    </script>

    <body>
        <br>
        <br>
        <div align="center">
            <div id="qrcode">
              <img src='iconLoading.gif' />
            </div>
            <div id="profile"></div>
        </div>

    </body>

</html>
```

QRscanner:qrscanner2.blade.php
We scan and get the data from qr code and send the data


```html
<!DOCTYPE HTML>
<html>

<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script src="../frontend/qr/jquery.qrcode-0.11.0.min.js" ></script>
    
</head>
    <section class="page-section">
		<div class="container">
				<h2 class="section-heading text-center">QR code scanner</h2>
				<div class="row setting-cards">
					<div class="col-centered col-md-8">
						<ul class="setting-card"> 
                        <li class="text-center">
                        
                 <?php $hashedid= HashUserID(Auth::user()->id); ?>
                  <p>passcode: <?php echo $hashedid; ?></p>
                  <p>Name: <?php echo Auth::user()->name;?></p>
                  <p>Email: <?php echo Auth::user()->email;?></p>
                   </li>
                        <li class="text-center">
    <div id="qr-reader" class="col-md-8"></div>
   
     <p id="login_mobile_scan_qrcode"></p>
     <p id="qrcodedoLogin"></p></li>
                        </ul>
						 <div id="qr-reader-results"></div>	
					</div>
				</div>
				

			

		</div>
		
	</section><section class="page-section">
	
		
	</section>
	

	
	
</body>

<script src="../frontend/qr/html5-qrcode.min.js" ></script>
<script>
function qrcodedoLogin(param){
      var url = param;
      console.log("qrcodedoLogin called",url);
    $.ajax({
        type: "POST" ,
        dataType: "json" ,
        url: url ,
        data:{
            //key:key
        },
        success:function(data) {
              if (data.status==1 ){
                  var qrcodeloginurl = data.msg;
            //scan successfull url recieved

                  $('#qrcodedoLogin').text("QR Loggin successfully");
               // console.log("qrcodeloginurl",qrcodeloginurl);
//qrcodedoLogin(qrcodeloginurl);

            } else  if (data.status==2 ){
//couldn't do login
               // alert(data.msg);

                  $('#qrcodedoLogin').text(data.msg);
            }
        }
    });
}
function login_mobile_scan_qrcode(param){
      var url = param;
	  if ("WebSocket" in window) {
		  var base = window.location.hostname;
		var ws = new WebSocket('wss://'+base+':8090');
                ws.onopen = function() {
            console.log("on WS open we sent the token to server");
			let params = (new URL(url)).searchParams;
			let urltoken = params.get('t'); 
            ws.send(JSON.stringify({ type: "client", step: 0, token: urltoken }));

                };
				ws.onmessage = function(event) {
					const data = JSON.parse(event.data);
					console.log(" client body",data);
					 const step = data.data && data.data.step;
                           if (step === 0) {
console.log("step",step);
						   }else if (step === 1) {
 ws.send(JSON.stringify({ type: "client", step: 1, username:'<?php echo $hashedid?>' }));
						   }

				}
				ws.onclose = function() {
                    console.log("Connection is closed...");
                };
	  } else {
                alert("WebSocket NOT supported by your Browser!");
            }
     // console.log("login_mobile_scan_qrcode called",url);
    
}
    function docReady(fn) {
        // see if DOM is already available
        if (document.readyState === "complete"
            || document.readyState === "interactive") {
            // call on next available tick
            setTimeout(fn, 1);
        } else {
            document.addEventListener("DOMContentLoaded", fn);
        }
    }

    docReady(function () {
        var resultContainer = document.getElementById('qr-reader-results');
        var lastResult, countResults = 0;
        function onScanSuccess(decodedText, decodedResult) {
            if (decodedText !== lastResult) {
                ++countResults;
                lastResult = decodedText;
                // Handle on success condition with the decoded message.
                console.log(`Scan result ${decodedText}`, decodedResult);
				 resultContainer.innerHTML += `<div>[${countResults}] - ${decodedText}</div>`;
            
           
                login_mobile_scan_qrcode(decodedText);
				 // Optional: To close the QR code scannign after the result is found
           // html5QrcodeScanner.clear();
            }
        }

        var html5QrcodeScanner = new Html5QrcodeScanner(
            "qr-reader", { fps: 10, qrbox: 250 });
        html5QrcodeScanner.render(onScanSuccess);
    });
</script>
```
[Gist](https://gist.github.com/sahilkashyap64/89e1a7d5d6114656cdac7f4a889aa823)
[Github of the projct](https://github.com/sahilkashyap64/qrlogin-php-websocket)