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
<?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
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
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
<!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
<!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>
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.
very cool!
thank you,I've added the github link, github.com/sahilkashyap64/qrlogin-...
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
I look for a
And then I login
/**
2 Ways to make entry in Starts table
*/
Unrelated to above here's a nice doc of laravel not so popular tips