Authenticating users
Application security is very important so Pusher provides a mechanism for authenticating a user’s access to a channel at the point of subscription.
This can be used both to restrict access to private channels, and in the case of presence channels notify subscribers of who else is also subscribed via presence events.
- The Authentication Process
- Channel Authentication endpoint
- Channel Authentication transport
- Implementing Authentication endpoints
The Authentication Process
The basic mechanism for doing this is broadly the same for both scenarios. The following sequence diagram shows the signing process:
- When a new instance of the
Pusherobject is created a new WebSocket object is created. - The WebSocket object connects to the Pusher WebSocket endpoint.
-
Once the connection has been established a universally unique
socket_idis returned to the Pusher JavaScript library. - A subscription is made to a
private-orpresence-channel. - The
private-orpresence-prefix identifies the channel as requiring authentication so a request is made to an authentication endpoint via either AJAX or JSONp. For more information see channel authentication transports. - If successful your application returns an authorisation string (see generating the authentication signature) to the Pusher JavaScript library signed with your Pusher secret.
- The
channel_nameand authentication signature is sent to Pusher over the WebSocket, which completes the authorisation if the authorisation string has been correctly signed.
For example, in both the subscribe calls below an authentication request will take place.
var pusher = new Pusher('APP_KEY');
var privateChannel = pusher.subscribe('private-channel');
var presenceChannel = pusher.subscribe('presence-channel');
The purpose of the authentication callback is so that you can check that the current user of your application has permission to access the channel that they are trying to subscribe to. How the user is authenticated depends on the decisions you make and the system you are developing.
Channel Authentication endpoint
The destination of the authentication request can be configured by setting a static property on the Pusher object.
Pusher.channel_auth_endpoint = '/pusher_auth.php';
The default value for this is: /pusher/auth
Channel Authentication transport
The authentication transport can be configured by setting a static property on the Pusher object.
Pusher.channel_auth_transport = 'jsonp';
The default value for this is: ajax.
‘ajax’ authentication transport
The HTTP POST request that is made to the authentication endpoint when a subscription takes place contains the following request parameters:
socket_id- A unique identifier for the specific client connection to Pusher
channel_name- The name of the channel being subscribed to
‘jsonp’ authentication transport
JSONp authentication can be used when the authentication endpoint is on a different domain to the web application. An additional parameter called callback will also be passed identifying the JavaScript function to be called from the script generated by the authentication endpoint.
Note: To use JSONp support, you must use Pusher’s client version 1.6 or above.
As described above you need to set the Pusher.channel_auth_transport to jsonp and optionally define a value for Pusher.channel_auth_endpoint.
When using JSONp the authentication the following parameters will be passed as query parameters (a GET request):
socket_id- A unique identifier for the specific client connection to Pusher
channel_name- The name of the channel being subscribed to
callback- The name of the function to be called in the script to be generated by the authentication endpoint
As discussed, the duty of the server is to check the user has permission to subscribe to the supplied channel. If the user does have permission then an authentication HTTP response must be returned in a JSON format with an authentication signature. We have a number of server libraries that make doing this really simple.
Note: If you don’t want to use one of the existing libraries, or there isn’t one in the technology you want to use, please see authenticating signatures or get in touch.
Implementing authentication endpoints
The following sections show how this is achieved for the different types of channels.
Types of channel
- Private channels - used to control access to a source of information
- Presence channels - used to control access, but also allows info about the user to be distributed to other users.
Implementing the auth endpoint for a private channel
Here are some examples of private channel authentication for different languages:
class PusherController < ApplicationController
protect_from_forgery :except => :auth # stop rails CSRF protection for this action
def auth
if current_user
response = Pusher[params[:channel_name]].authenticate(params[:socket_id])
render :json => response
else
render :text => "Forbidden", :status => '403'
end
end
end
global $user;
if ($user->uid)
{
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
echo $pusher->socket_auth($_POST['channel_name'], $_POST['socket_id']);
}
else
{
header('', true, 403);
echo "Forbidden";
}
if ( is_user_logged_in() )
{
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
echo $pusher->socket_auth($_POST['channel_name'], $_POST['socket_id']);
}
else
{
header('', true, 403);
echo "Forbidden";
}
var express = require( 'express' );
var Pusher = require( 'pusher' );
var app = express.createServer( express.logger() );
app.use( express.bodyParser() );
var pusher = new Pusher( { appId: APP_ID, key: APP_KEY, secret: APP_SECRET } );
app.post( '/pusher/auth', function( req, res ) {
var socketId = req.body.socket_id;
var channel = req.body.channel_name;
var auth = pusher.auth( socketId, channel );
res.send( auth );
} );
var port = process.env.PORT || 5000;
app.listen( port );
using PusherServer;
public class MyController : Controller
{
public ActionResult Auth(string channel_name, string socket_id)
{
var pusher = new Pusher(APP_ID, APP_KEY, APP_SECRET);
var auth = pusher.Authenticate( channel_name, socketId );
var json = auth.ToJson();
return new ContentResult { Content = json, ContentType = "application/json" };
}
}
class AuthHandler(webapp.RequestHandler):
def post(self):
channel_name = self.request.get('channel_name')
socket_id = self.request.get('socket_id')
p = pusher.Pusher(app_id=APP_ID, key=APP_KEY, secret=APP_SECRET)
auth = p[channel_name].authenticate(socket_id)
json_data = json.dumps(auth)
self.response.out.write(json_data)
def main():
application = webapp.WSGIApplication([('/pusher/auth', AuthHandler)])
The generated JSON should look as follows:
{
"auth":"278d425bdf160c739803:a99e78e7cd40dcd0d4ae06be0a5395b6cd3c085764229fd40b39ce92c39af33e"
}
Implementing the auth endpoint for a presence channel
Authentication of a presence channel is performed in exactly the same way as a private channel but the JSON response must have a channel_data property containing information that you wish to share about the current user.
Here are some examples of presence channel authentication for different languages:
class PusherController < ApplicationController
protect_from_forgery :except => :auth # stop rails CSRF protection for this action
def auth
if current_user
response = Pusher[params[:channel_name]].authenticate(params[:socket_id], {
:user_id => current_user.id, # => required
:user_info => { # => optional - for example
:name => current_user.name,
:email => current_user.email
}
})
render :json => response
else
render :text => "Forbidden", :status => '403'
end
end
end
global $user;
if ($user->uid)
{
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
$presence_data = array('name' => $user->name);
echo $pusher->presence_auth($_POST['channel_name'], $_POST['socket_id'], $user->uid, $presence_data);
}
else
{
header('', true, 403);
echo( "Forbidden" );
}
if ( is_user_logged_in() )
{
global $current_user;
get_currentuserinfo();
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
$presence_data = array('name' => $current_user->display_name);
echo $pusher->presence_auth($_POST['channel_name'], $_POST['socket_id'], $current_user->ID, $presence_data);
}
else
{
header('', true, 403);
echo( "Forbidden" );
}
using PusherServer;
public class MyController : Controller
{
public ActionResult Auth(string channel_name, string socket_id)
{
var pusher = new Pusher(APP_ID, APP_KEY, APP_SECRET);
var channelData = new PresenceChannelData() {
user_id: "unique_user_id",
user_info: new {
name = "Mr Pusher"
twitter_id = "@pusher"
}
};
var auth = pusher.Authenticate( channelName, socketId, channelData );
var json = auth.ToJson();
return new ContentResult { Content = json, ContentType = "application/json" };
}
}
var express = require( 'express' );
var Pusher = require( 'pusher' );
var app = express.createServer( express.logger() );
app.use( express.bodyParser() );
var pusher = new Pusher( { appId: APP_ID, key: APP_KEY, secret: APP_SECRET } );
app.post( '/pusher/auth', function( req, res ) {
var socketId = req.body.socket_id;
var channel = req.body.channel_name;
var presenceData = {
user_id: 'unique_user_id',
user_info: {
name: 'Mr Pusher'
twitter_id: '@pusher'
}
};
var auth = pusher.auth( socketId, channel, presenceData );
res.send( auth );
} );
var port = process.env.PORT || 5000;
app.listen( port );
class AuthHandler(webapp.RequestHandler):
def post(self):
channel_name = self.request.get('channel_name')
socket_id = self.request.get('socket_id')
channel_data = {'user_id': socket_id}
channel_data['user_info'] = {'name':'Test Name'}
p = pusher.Pusher(app_id=APP_ID, key=APP_KEY, secret=APP_SECRET)
auth = p[channel_name].authenticate(socket_id, channel_data)
json_data = json.dumps(auth)
self.response.out.write(json_data
def main():
application = webapp.WSGIApplication([('/pusher/auth', AuthHandler)])
The generated JSON should look similar to the private channel JSON with an additional channel_data property which should be valid JSON encoded as a string:
{
"auth":"49e26cb8e9dde3dfc009:a8cf1d3deefbb1bdc6a9d1547640d49d94b4b512320e2597c257a740edd1788f",
"channel_data":"{\"user_id\":\"Phil Leggetter\",\"user_info\":{\"name\":\"Phil Leggetter\",\"imageUrl\":\"http:\\\/\\\/www.gravatar.com\\\/avatar\\\/ecc56977271e781991b6172c16248459?s=80&d=mm&r=g\"}}"
}
JSONp Authentication endpoints
As discussed above the channel_auth_transport should be set to jsonp in order to use JSONp authentication. If JSONp authentication is being used then it is highly likely that the endpoint is on a different domain so the channel_auth_endpoint should also be set.
<script src="http://js.pusher.com/2.0/pusher.min.js"></script>
<script>
Pusher.channel_auth_transport = 'jsonp';
Pusher.channel_auth_endpoint = 'http://myserver.com/pusher_jsonp_auth';
</script>
Then you would subscribe to a private or presence channel, as needed.
<script>
var pusher = new Pusher('MY_PUSHER_KEY');
var channel = pusher.subscribe('private-test_channel');
channel.bind('greet', function(data) {
alert(data.greeting);
});
</script>
As demonstrated in the sequence diagram the server would then receive a call. Authentication works in much the same way as it does with an AJAX request. However, the request parameters are passed in the query string, and the authentication response must be a string which represents a JavaScript function call which indicates the authentication response. The function to be called is identified by the callback parameter in the query string.
The following examples demonstrate how private channels are authenticated and how the response is wrapped in a callback. Presence channels are authenticated as demonstrated in the Presence endpoint section and the callbacks wrapped in exactly the same way as shown here.
class PusherController < ApplicationController
protect_from_forgery :except => :auth # stop rails CSRF protection for this action
def auth
if current_user
auth = Pusher[params[:channel_name]].authenticate(params[:socket_id])
render :text => params[:callback] + "(" + auth.to_json + ")"
else
render :text => "Forbidden", :status => '403'
end
end
end
global $user;
if ($user->uid)
{
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
$auth = $pusher->socket_auth($_GET['channel_name'], $_GET['socket_id']);
$callback = str_replace('\\', '', $_GET['callback']);
echo($callback . '(' . $auth . ');');
}
else
{
header('', true, 403);
echo "Forbidden";
}
if ( is_user_logged_in() )
{
$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
$auth = $pusher->socket_auth($_GET['channel_name'], $_GET['socket_id']);
$callback = str_replace('\\', '', $_GET['callback']);
echo($callback . '(' . $auth . ');');
}
else
{
header('', true, 403);
echo "Forbidden";
}
