I'm using laravel 5.3
+ passport for authorization, Laravel
is my back-end API which is restful
.
front-end is written in angular.js
which communicate with API with rest requests.
For Real-time notifications, I've used laravel
broadcasting events + redis
, and socket.io
for socket server and socket client in angular.js.
I want to authorize these events and I've done it far as I could :
BroadcastServiceProvider :
public function boot()
{
Broadcast::routes(['middleware' => ['auth:api']]);
Broadcast::channel('App.User.*', function ($user, $userId)
{
return (int) $user->id === (int) $userId;
});
Broadcast::channel('notifs.*', function ($user, $userId) {
return $user->id === (int) $userId;
});
}
This is my socket.js code which runs my socket server :
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.psubscribe('*', function(err, count) {});
redis.on('pmessage', function(subscribed, channel, message) {
console.log(channel);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function () {
console.log('Listening on Port 3000');
});
redis.on("error", function (err) {
console.log(err);
});
The problem is I don't know how to authenticate these broadcasting events in socket server and also how to authorize the user in angular.js (SPA)
to listen to these events.
I'd appreciate any help.
I'd definitely take a look at socketio-auth.
This module provides hooks to implement authentication in socket.io
without using querystrings to send credentials, which is not a good
security practice.
Another approach I took recently was simple token based authentication using JWT tokens (njwt).
I did not want to recreate the authentication code that checks user credentials within Node.js. (which in my case can't even connect to the database anyway). Rather, I'd let the PHP application that was using the socket leverage its already established authentication system. Passing a signed token with the socket connect requests.
Your node.JS code might look something like...
primus.on('connection', function (spark) {
logger.debug('primus event connection. spark id: ' + spark.id);
spark.on('data', function(data) {
var action = data.action;
njwt.verify(data.token, JWT_SECRET, function(err, verifiedJwt) {
if (err) {
logger.warn('Bad JWT Token! ' + spark.id + ' Error: ' + err);
spark.user = {id:null, is_authed: false, is_admin: false, application_ini: null};
spark.end('Bad Token Request');
return; //->
}
spark.user = { 'id': verifiedJwt.body['user_id'],
'is_authed': verifiedJwt.body['is_authed'],
'application_ini': verifiedJwt.body['application_ini'],
'is_admin': verifiedJwt.body['is_admin']};
sockoasRooms.connect(spark.id, spark.user.application_ini, spark.user.id);
switch (action) {
...
And then on the PHP side, you'll need some code for generating the JWT tokens, but use is very simple. Something like:
<?php
$tokenPayload = [ 'user_id' => ($this->currentUser) ? $this->currentUser->getId() : 0,
'is_authed' => ($this->currentUser) ? true : false,
'application_ini' => (string) APPLICATION_INI,
'is_admin' => (bool) ($this->currentUser) ? $this->currentUser->isAdministrator() : false,
'now' => time()
];
$jwtToken = \OAS_JWT::encode($tokenPayload, SOCK_OAS_JWT_KEY);
?>
$(document).ready(function() {
primus = Primus.connect('ws://<?=SOCK_OAS_IP?>:<?=SOCK_OAS_PORT?>/_admin');
primus.on('open', function () {
showConnected();
// Send request to subscribe
primus.write({action: 'dashboard-dump', token: '<?=$jwtToken?>'});
consoleWrite('Connected to dashboard.');
});
You can evaluate the time component to avoid replay attacks. Anyway, sounds like this approach might meet your needs.
Off topic but I'd also suggest taking a look at primus. It acts as a "universal wrapper for real-time frameworks". This lets you abstract things in a way so you could swap out the socket libraries with no hassle. Might be a little lower level (engine.IO) than what you are using though.