The Definitive Django 🤝Pusher Guide
Learn how to integrate Private and Presence channels with Django, including auth and configuration.

We did an online workshop with Pusher and demonstrated all these concepts LIVE. If you prefer video over reading, go check it out: https://www.youtube.com/watch?v=p62JVs87r1g&t=730s
For this tutorial, you need a Pusher account. You can create one for free here.
Pusher makes it incredibly simple to get started sending real time notifications with public channels. But Private and Presence channels require a little bit more work, specially because they require to setup authentication and deal with CSRF (cross site request forgery) tokens.
This tutorial provides a step guide to connect and authenticate Private and Presence channels, specially addressing those complicated points like authentication and CSRF prevention.
Getting Started
We’ve built a sample app demonstrating all the topics covered in this post and you can find it here: https://github.com/rmotr/django-pusher-demo (live demo).
We’re using Django 2.0 and Python 3.6. The Django project structure isn’t special and you can use your own or copy the one used in the sample repo.
User authentication is the default provided by Django auth. We’re also using the official Pusher Python library to authenticate and trigger events.
Private channels (and pusher authentication)
Pusher’s Private Channels let you restrict access to specific channels and control who can subscribe and receive events from such channels. Let’s do a step by step explanation of how authentication works, using as an example a user who wants to subscribe to a channel named private-team-design (private channels’ names always must start with private-). Only users that are part of the “Designers” team can subscribe to that channel.

Pusher Auth Life Cycle
Step 0: Setup pusher auth’s endpoint
Pusher will let you configure the URL used to authenticate your users. It’ll be a regular Django view that must either accept or reject the user’s auth attempt. When you instantiate pusher, you can specify a custom auth URL. In this case we’re using the url template tag to reverse to our view:
var pusher = new Pusher('app_key', {
authEndpoint: '{% url 'pusher_auth' %}', {# We use URL reverse #}
auth: {
headers: {
'X-CSRFToken': "{{ csrf_token }}" {# Important, more details below #}
}
}
});
Somewhere, in your urls.py you’ll include the pattern:
urlpatterns = [
# ...
path('pusher_auth', views.pusher_auth, name='pusher_auth'),
# ...
]
Step 1: The javascript code to subscribe
Everything starts with the client, when it tries to subscribe to that private channel.
var channel = pusher.subscribe('private-team-design');
Step 2: Pusher tries to authenticate the user
Pusher will try to authenticate the user using the Django URL specified in step 0 ({% url 'pusher_auth' %}). It’ll send an AJAX request to that URL and you must decide to either authorize access or forbid it. Next step.
Step 3: Implement the view that authenticates the user
As we said, this is just a regular Django view. You have all the resources available to decide if you want to allow access to the given user. As usual, request.user contains the authenticated user (if any). If the user is not authorized, it must return a 403 Forbidden response.
If the user is authorized, it must return a 200 response with a special JSON payload, which must include a token generated by pusher to confirm that user’s access to the channel. This is the code:
from pusher import Pusher
def pusher_auth(request):
if not request.user.is_authenticated:
return HttpResponseForbidden()
if not request.user.is_member_of_team('designers'):
return HttpResponseForbidden()
pusher_client = Pusher(APP_ID, API_KEY, SECRET_KEY, CLUSTER)
# We must generate the token with pusher's service
payload = pusher_client.authenticate(
channel=request.POST['channel_name'],
socket_id=request.POST['socket_id'])
return JsonResponse(payload)
Interlude: CSRF Prevention
Django includes CSRF prevention out of the box (if the CsrfViewMiddleware middleware is included in your settings). That means that EVERY POST request must include the CSRF token to validate it. This is an important security mechanism and you shouldn’t disable it. As you’ve seen in Step 0, setting it up is rather simple. By default, all Django templates will include the csrf_token value in their context, so you can just set it when you’re instantiating the Pusher’s client with the header 'X-CSRFToken'. That means that Pusher will include that header when it tries to authenticate the user, and the CsrfViewMiddleware will check for it when the POST request is received.
Step 4:Â Profit
If authentication went well, the view we’ve defined will return a 200 Ok HTTP response and Pusher will authenticate the user. You’re now able to start receiving different events for that private channel. There are two important events triggered by Pusher that will indicate, to your client-side application, if the auth was successful or not: pusher:subscription_succeeded and pusher:subscription_error respectively. Example:
var channel = pusher.subscribe('private-team-design');
channel.bind('pusher:subscription_succeeded', function() {
console.log("Auth went OK!")
});
channel.bind('pusher:subscription_error', function() {
console.log("Auth rejected by server")
});
Presence channels
Presence channels are like regular Private channels, but they also include information about the “members” of the channel. It can be useful to, for example, display all the users connected to that given channel. The authentication mechanism of Presence channels is analogous to private ones, with a small difference: when users are authenticated correctly, you can include additional information about them. That information will then allow everybody know about the channel’s members.
Presence channels’ names must be prefixed with presence-. For example, changing the private team channel for designers to a presence one would be:
var channel = pusher.subscribe('presence-team-design');
The authentication is similar, but we’ll also include additional user info:
from pusher import Pusher
def pusher_auth(request):
if not request.user.is_authenticated:
return HttpResponseForbidden()
if not request.user.is_member_of_team('designers'): # Regular ORM query
return HttpResponseForbidden()
pusher_client = Pusher(APP_ID, API_KEY, SECRET_KEY, CLUSTER)
# We must generate the token with pusher's service
payload = pusher_client.authenticate(
channel=request.POST['channel_name'],
socket_id=request.POST['socket_id'],
custom_data={
'user_id': request.user.id,
'user_info': { # We can put whatever we want here
'username': request.user.username,
'first_name': request.user.first_name,
'last_name': request.user.last_name,
'twitter_handle': request.user.twitter_handle
}
})
return JsonResponse(payload)
As you can see in the example above, we’re including the parameter custom_data with the user’s ID and extra information we want to share about that user. If the authentication is successful, the event pusher:subscription_succeeded will include a members list with info about currently subscribed clients.
Additionally, there are two other events pusher:member_added and pusher:member_removed that will be triggered when a member has been added or removed with information about them.
Triggering events
Once auth is stablished, triggering events to subscribed clients is simple with Pusher’s Python library and the triggerevent. For example, using the private/presence “Team Design” channel from our example, we might want to trigger a notification when a new illustration is uploaded to it:
from pusher import Pusher
def upload_illustration(request):
# ... more code here
pusher_client = Pusher(APP_ID, API_KEY, SECRET_KEY, CLUSTER)
pusher_client.trigger(
'presence-team-design',
'illustration-uploaded', {
'author': request.user.username,
'name': request.POST['illustration_name']
'path': request.POST['illustration_path']
})
# ... more code...
return render(...)
Improving auth with a Strategy Pattern
Once your app grows a little bit and you start supporting multiple channels, you’ll need to think of better designs for your Auth view. In our experience, a good solution includes: namespaces and the Strategy Pattern.
Namespaces are one honking great idea — let’s do more of those! (The Zen of Python, Tim Peters,
import this)
Here’s an example implementation. You can of course tweak it at wish, the following example tries to keep it simple enough to highlight the concept: