Last week I entered the first ever dev.to contest and submitted a serverless multiplayer clicker game.
It would be awesome to get your ❤️ & 🦄 on my entry-post
I'd also like to give you some know-how in return.
Custom Authentication for Pusher
Pusher allows for custom authorisers that can save you a bunch of requests.
What
An authoriser for Pusher is just a function that takes a context
, a socketId
and a callback
as arguments.
function customAuth(context, socketId, callback) {
...
}
It gets called by the Pusher client when you try to join a private or a presence channel.
The regular implementation sends a HTTP request to your backend and you have to return a token that the client will use to connect to Pusher.
The socketId
is the current socket ID of the client.
The callback
needs to be called when the authentication is done.
// The first argument needs to be false if everything went well
// and the second one needs to include the credentials from the server
callback(false, authCredentials);
// The first argument needs to be true if the authentication failed
// and the second one can be a error description
callback(true, errorText);
Why
The authoriser function is called every time a client subscribes to a private or presence channel and the default one sends a HTTP request every time, so depending on the amount of channels one client joins in short time it could be a good idea to consolidate these requests.
Also, as in the game I created, it could be that the client gets the information about which channel to join from the server. So you would end up with one request to get the channel and one request to authenticate it. With a custom authoriser you could create the authCredentials
in the same request that chooses the channel.
How
Regular Auth
The regular auth procedure is as follows:
- The client connects to Pusher and gets a
socketId
- The client tries to subscribe to a private or presence channel
- The client sends its
socketId
and thechannelName
to the server (your server, not the Pusher server) - The server asks the Pusher API to authenticate the
socketId
for thechannelName
- The Pusher API creates
authCredentials
which get send back to the client - The client uses the
authCredenatials
to subscribe to the channel
The server side authentication looks like this
const authCredentials = pusher.authenticate(
socketId,
channelName,
{user_id: socketId}
);
The arguments can come in via HTTP query parameters or body, the authCredentials
need to be sent back to the client via HTTP.
Custom Auth
A custom version, like I used in my game, could look different.
Before
- The client connects to Pusher and gets a
socketId
- The client requests a
channelName
from the server - The client gets a
channelName
from the server - The client tries to subscribe to the Pusher channel with the
channelName
- The client gets
authCredentials
from the server - The client subscribes to the Pusher channel with
authCredentials
After
- The client connects to Pusher and gets a
socketId
- The client requests a
channelName
from the server - The client gets a
channelName
andauthCredentials
from the server - The client subscribes to the Pusher channel with the
authCredentials
So we need two new parts. A new authoriser, that wouldn't call the server but use some local data for authentication and a way to get the channelName
and authCredentials
from the server in one request.
Lets start from the back, how to get the two informations from the server.
For this we could add a new method to the Pusher client.
pusher.subscribeServerChannel = function() {
const {socket_id} = pusher.connection;
return fetch("/getChannel?socketId=" + socket_id)
.then(r => r.json())
.then(({channelName, authCredentials}) => {
// used by the authoriser later
pusher.config.auth.preAuth[channelName] = authCredentials;
// calls the autoriser
return pusher.subscribe(channelName);
})
};
This function tries to subscribe to a channel it requests from the server (your back-end). The GET /getChannel
endpoint needs the socketId
to create the authCredentials
then channelName
will also be created server side.
Next we need the new authoriser, also client side.
First get the old ones and add the new one to them. All before we create a connection.
const supportedAuthorizers = Pusher.Runtime.getAuthorizers();
supportedAuthorizers.preAuthenticated = function(context, socketId, callback) {
const { authOptions, channel } = this;
// getting the credentials we saved in subscribeServerChannel
const authCredentials = authOptions.preAuth[channel.name];
if (authCredentials) return callback(false, authCredentials);
callback(true, "You need to pre-authenticate for channel: " + channel.name);
};
Pusher.Runtime.getAuthorizers = () => supportedAuthorizers;
// Later when the connection is created
const pusher = new Pusher(APP_KEY, {
auth: {
preAuth: {} // where the auth credentials will be stored
},
// set the transport to the new authoriser
authTransport: "preAuthenticated",
});
Last but not least the server endpoint that creates the channelName
and handles authentication.
server.get("/getChannel", (req, res) => {
const {socketId} = req.query;
const channelName = "private-" + Math.random();
const authCredentials = pusher.authenticate(socketId, channelName, {user_id: socketId});
res.send({channelName, authCredentials});
});
On the client we can now simply call subscribeServerChannel
and get a pusher channel in return.
const pusher = new Pusher(APP_KEY, {
auth: { preAuth: {} },
authTransport: "preAuthenticated",
...
});
pusher.connection.bind("connected", async () =>
const channel = await pusher.subscribeServerChannel();
channel.bind("my:event", ...);
);
And that's basically it.
You make one request and get all the data you need to join the channel with the client.
Conclusion
The Pusher client is a very flexible piece of software that allows you to modify authentication-flow to your liking. This eases integration rather much and allows for some performance tweaks in the long run.
Contest
Also, if you liked this post:
Top comments (1)
We where having authentication request spikes when the websocket server was restarted. All the clients were trying to authenticate at the same time and everriding the customHandler was the solution:
In this solution we are spreading the requests over time.
Thanks for the tip!!!