
QR login using websocket in laravel
package used:
- cboden/ratchet
Our app and websocket are on different port.
Let's setup command to run the websocket
<?phpnamespaceApp\Console\Commands;useIlluminate\Console\Command;useRatchet\Server\IoServer;useRatchet\Http\HttpServer;useRatchet\WebSocket\WsServer;useApp\Http\Controllers\WebSocketController;useReact\EventLoop\Factory;useReact\Socket\SecureServer;useReact\Socket\Server;classWebSocketServerextendsCommand{/** * 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 */publicfunction__construct(){parent::__construct();}/** * Execute the console command. * * @return mixed */publicfunctionhandle(){//for local// $this->forlocal();//for prod server$this->forprodserver();}publicfunctionforlocal(){$server=IoServer::factory(newHttpServer(newWsServer(newWebSocketController())),8090);$server->run();}publicfunctionforprodserver(){$loop=Factory::create();$webSock=newSecureServer(newServer('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=newIoServer(newHttpServer(newWsServer(newWebSocketController())),$webSock);$loop->run();}}
Let's setup the routes
web.php
<?phpRoute::get('/qrtesting', 'Admin\QRLoginTwoController@qrtesting');Route::post('web/loginws', 'Admin\QRLoginTwoController@loginWS');Route::get('/qrscanner', 'Admin\QRLoginTwoController@qrscanner2');
Controller
<?phpuseApp\Http\Controllers\Controller;useIlluminate\Http\Request;useIlluminate\Support\Facades\Auth;classQRLoginTwoControllerextendsController{publicfunctionqrtesting(){returnview('frontend.qrtesting');}publicfunctionqrscanner2(){if(Auth::check()){$login=true;returnview('frontend.qrscanner2',compact('login'));}returnredirect()->route('home');}publicfunctionloginWS(Request$request){$key=$request['key'];if(empty($key)){$return=array('status'=>2,'msg'=>'key not provided');returnresponse()->json($return,200);}$userid=UnHashUserID($key);try{$user=Auth::loginUsingId($userid,true);$return=array('status'=>1,'msg'=>'success','jwt'=>1,'user'=>$user);returnresponse()->json($return,200);}catch(Exception$exception){returnresponse()->json(['status'=>2,'success'=>false,'message'=>'Some Error occured','error'=>$exception->getMessage(),'response_code'=>200,],200);}}}?>
WebSocketController.php
<?phpnamespaceApp\Http\Controllers;useIlluminate\Support\Str;useRatchet\MessageComponentInterface;useRatchet\ConnectionInterface;classWebSocketControllerextendsControllerimplementsMessageComponentInterface{private$connections=[];private$clients;private$cache;publicfunction__construct(){$this->clients=new\SplObjectStorage();// memory cache$this->cache=array();}publicfunctionmulticast($msg){foreach($this->clientsas$client)$client->send($msg);}publicfunctionsend_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 */functiononOpen(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 */functiononClose(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 */functiononError(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 */functiononMessage(ConnectionInterface$from,$msg){$numRecv=count($this->clients)-1;echosprintf('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){case0:// 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;case1:$myArray3[]=(object)['step'=>2];$myArray4[]=(object)['step'=>2,'username'=>$obj->username];foreach($this->cacheas$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));}}elseif($type=='server'){// echo "hello inside server";//to get the QR logoswitch($obj->step){case0:$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
<!DOCTYPE HTML><html><head><scriptsrc="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><scriptsrc="../frontend/qr/jquery.qrcode-0.11.0.min.js"></script><scripttype="text/javascript">$(document).ready(function(){initiate();});functioninitiate(){if("WebSocket"inwindow){varbase=window.location.hostname;// var ws = new WebSocket('wss://'+base+':8090');varws=newWebSocket('wss://'+base+':8090');console.log(ws);ws.onopen=function(){ws.send(JSON.stringify({type:"server",code:0,step:0}));};ws.onmessage=function(evt){constdata=JSON.parse(event.data);//console.log("datafromservver",data);conststep=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");}elseif(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 confirmedfunctionis_loginfun(param){varkey=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){varuid=data.jwt;varuser=data.user;console.log("user",user);console.log("login successfull",uid);alert("login successfull",uid);window.location.href='/';}elseif(data.status==2){alert(data.msg);}}});}</script><body><br><br><divalign="center"><divid="qrcode"><imgsrc='iconLoading.gif'/></div><divid="profile"></div></div></body></html>
QRscanner:qrscanner2.blade.php
We scan and get the data from qr code and send the data
<!DOCTYPE HTML><html><head><scriptsrc="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><scriptsrc="../frontend/qr/jquery.qrcode-0.11.0.min.js"></script></head><sectionclass="page-section"><divclass="container"><h2class="section-heading text-center">QR code scanner</h2><divclass="row setting-cards"><divclass="col-centered col-md-8"><ulclass="setting-card"><liclass="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><liclass="text-center"><divid="qr-reader"class="col-md-8"></div><pid="login_mobile_scan_qrcode"></p><pid="qrcodedoLogin"></p></li></ul><divid="qr-reader-results"></div></div></div></div></section><sectionclass="page-section"></section></body><scriptsrc="../frontend/qr/html5-qrcode.min.js"></script><script>functionqrcodedoLogin(param){varurl=param;console.log("qrcodedoLogin called",url);$.ajax({type:"POST",dataType:"json",url:url,data:{//key:key},success:function(data){if(data.status==1){varqrcodeloginurl=data.msg;//scan successfull url recieved$('#qrcodedoLogin').text("QR Loggin successfully");// console.log("qrcodeloginurl",qrcodeloginurl);//qrcodedoLogin(qrcodeloginurl);}elseif(data.status==2){//couldn't do login// alert(data.msg);$('#qrcodedoLogin').text(data.msg);}}});}functionlogin_mobile_scan_qrcode(param){varurl=param;if("WebSocket"inwindow){varbase=window.location.hostname;varws=newWebSocket('wss://'+base+':8090');ws.onopen=function(){console.log("on WS open we sent the token to server");letparams=(newURL(url)).searchParams;leturltoken=params.get('t');ws.send(JSON.stringify({type:"client",step:0,token:urltoken}));};ws.onmessage=function(event){constdata=JSON.parse(event.data);console.log(" client body",data);conststep=data.data&&data.data.step;if(step===0){console.log("step",step);}elseif(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);}functiondocReady(fn){// see if DOM is already availableif(document.readyState==="complete"||document.readyState==="interactive"){// call on next available ticksetTimeout(fn,1);}else{document.addEventListener("DOMContentLoaded",fn);}}docReady(function(){varresultContainer=document.getElementById('qr-reader-results');varlastResult,countResults=0;functiononScanSuccess(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();}}varhtml5QrcodeScanner=newHtml5QrcodeScanner("qr-reader",{fps:10,qrbox:250});html5QrcodeScanner.render(onScanSuccess);});</script>
Top comments(6)

Sir it is working fine on local server
but when i upload it to the remote server it is giving me error
WebSocket {url: 'wss://qberg.mn/:8080', readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, …}
qrtest:48 WebSocket connection to 'wss://qberg.mn/:8080' failed: Error during WebSocket handshake: Unexpected response code: 404
(anonymous) @ qrtest:48
dispatch @ jquery.min.js:2
v.handle @ jquery.min.js:2
qrtest:90 Connection is closed...

hi Naeem, Apologies for late reply. have you setup-ed the the wss properly on server.
- The error message you shared says 404, maybe the url is wrong.
- if it is not, Maybe try pinging the url using postman

How do I get the username that scanned the qr to be saved in a "Starts" table along with the time that person scanned the qr code

Get username who scanned the qr code
And make entry in Starts table?
If i'm understanding it wrong, kindly correct me.
here's the solution:
in this function
publicfunctionloginWS(Request$request)
I look for a
$key//$key is just hashed userID,i unhash it
And then I login
$user=Auth::loginUsingId($userid,true);// $user contains all the user details
/**
2 Ways to make entry in Starts table
- laravel provides ways to overRide login function,
- Simply add Starts Model in top and make an entry like this
useApp\Models\Starts;//place this query right after $user = Auth::loginUsingId($userid, true);$Starts=Starts::create(['userId'=>$userid,'somerandominfofromuser'=>$user->dummycolum,//asumming createdat or updatedat fields are autofilled and the model is properly setuped]);
*/
Unrelated to above here's a nice doc oflaravel not so popular tips
For further actions, you may consider blocking this person and/orreporting abuse