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

The basic mechanism for doing this is broadly the same for both scenarios. The following sequence diagram shows the signing process:

  1. When a new instance of the Pusher object is created a new WebSocket object is created.
  2. The WebSocket object connects to the Pusher WebSocket endpoint.
  3. Once the connection has been established a universally unique socket_id is returned to the Pusher JavaScript library.

  4. A subscription is made to a private- or presence- channel.
  5. The private- or presence- 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.
  6. If successful your application returns an authorisation string (see generating the authentication signature) to the Pusher JavaScript library signed with your Pusher secret.
  7. The channel_name and 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.

new Pusher('app_key', { authEndpoint: '/pusher_auth.php' });

The default value for this is: /pusher/auth

In order to connect to a private or presence channel using libPusher, you first need to configure your server authorisation URL.

_client.authorizationURL = [NSURL URLWithString:@"http://www.yourserver.com/authorise"];

When you attempt to connect to a private or presence channel, libPusher will make a form-encoded POST request to the above URL, passing along the socket_id and channel_name as parameters. Prior to sending the request, the Pusher delegate will be notified, passing in the NSMutableURLRequest instance that will be sent.

Its up to you to configure the request to handle whatever authentication mechanism you are using. In this example, we simply set a custom header with a token which the server will use to authenticate the user before proceeding with authorisation.

- (void)pusher:(PTPusher *)pusher willAuthorizeChannelWithRequest:(NSMutableURLRequest *)request
{
  [request setValue:@"some-authentication-token" forHTTPHeaderField:@"X-MyCustom-AuthTokenHeader"];
}

Channel Authentication transport

Channel authentication transport is only applicable to the Pusher JavaScript library.

The authentication transport can be configured by passing an option to the Pusher constructor.

new Pusher('app_key', { authTransport: '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 authTransport Pusher constructor option to jsonp and optionally pass authEndpoint value.

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( 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.authenticate( 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( 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.authenticate( 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

JSONp auth endpoints are only applicable to the Pusher JavaScript library.

As discussed above the authTransport 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 authEndpoint should also be set.

<script src="//js.pusher.com/2.2/pusher.min.js"></script>
<script>
  var pusher = new Pusher('MY_PUSHER_KEY', {
    authTransport: 'jsonp',
    authEndpoint: 'http://myserver.com/pusher_jsonp_auth'
  });
</script>

Then you would subscribe to a private or presence channel, as needed.

<script>
  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. Because the JavaScript code returned in the response is executed by the browser, the response needs to have a content type of application/javascript.

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 + ")", :content_type => 'application/javascript'
    else
      render :text => "Forbidden", :status => '403'
    end
  end
end
// Express.js setup
// http://expressjs.com/

...

app.get("/pusher/auth", function( req, res ) { 
  var query = req.query; 
  var socketId = query.socket_id; 
  var channel = query.channel_name; 
  var callback = query.callback;

  var presenceData = { 
    user_id: "some_id", 
    user_info: { 
      name: "John Smith" 
    } 
  }; 
  
  var auth = JSON.stringify(pusher.auth( socketId, channel, presenceData )); 
  var cb = callback.replace(/\"/g,"") + "(" + auth + ");";

  res.set({ 
    "Content-Type": "application/javascript" 
  }); 

  res.send(cb); 
});
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']);
  header('Content-Type: application/javascript');
  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']);
  header('Content-Type: application/javascript');
  echo($callback . '(' . $auth . ');');
}
else
{
  header('', true, 403);
  echo "Forbidden";
}

Have you tried using the search to find what you’re after? If you still have a question then get in touch with us and let us help you out.