Sahil kashyap
Posted on October 21, 2021
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>
💖 💪 🙅 🚩
Sahil kashyap
Posted on October 21, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.