PieSocket Authentication
Anyone holding your API key, public by design unlike your API secret, can subscribe to any of your channels and see everything published on them. Private channels and forced authentication are how you restrict that to users your own backend has approved.
Private channels
Channels prefixed private- require a valid JWT to connect.
Forced authentication
To require a JWT on every channel, not just private- ones, open your PieSocket from the dashboard and enable "forced authentication" from its Settings tab.
Rejected connections
A connection that needs a JWT but doesn't supply a valid one is rejected immediately, see Errors in the protocol reference for the exact error frame.
Getting a JWT to your client
There are two ways to do this:
- Automatic, configure your SDK with an
authEndpointon your own backend, and it fetches the token for you whenever a connection needs one. - Manual, generate the JWT yourself and pass it directly when connecting.
Automatic: let the SDK fetch it for you
All four official SDKs, can fetch a JWT from your own backend automatically, so you don't have to write any client-side token-fetching code yourself.
Configure it on the client:
const piesocket = new PieSocket({
version: 4,
clusterId: 'YOUR_CLUSTER_ID',
apiKey: 'YOUR_API_KEY',
authEndpoint: '/broadcasting/auth', // this is already the default
authHeaders: { 'X-CSRF-TOKEN': csrfToken },
});
PieSocketOptions options = new PieSocketOptions();
options.setVersion("4");
options.setClusterId("YOUR_CLUSTER_ID");
options.setApiKey("YOUR_API_KEY");
options.setAuthEndpoint("https://yourapp.com/broadcasting/auth");
PieSocketOptions options = PieSocketOptions();
options.setVersion("4");
options.setClusterId("YOUR_CLUSTER_ID");
options.setApiKey("YOUR_API_KEY");
options.setAuthEndpoint("https://yourapp.com/broadcasting/auth");
let options = PieSocketOptions()
options.setVersion(version: "4")
options.setClusterId(clusterId: "YOUR_CLUSTER_ID")
options.setApiKey(apiKey: "YOUR_API_KEY")
options.setAuthEndpoint(authEndpoint: "https://yourapp.com/broadcasting/auth")
authEndpoint only has a default value (/broadcasting/auth, resolved against the current page) in the JavaScript SDK, since it's the only one running in a browser with an implicit origin to resolve a relative path against. The other three require an absolute URL.
Whenever the SDK connects to a channel, it decides whether to call authEndpoint in this order:
- If you passed a
jwtoption directly, that's used as-is,authEndpointis never called for that connection. - Otherwise, the channel needs to be "guarded", either its name is prefixed
private-, or you setforceAuth: trueon the client (which guards every channel, not justprivate-ones). If it isn't guarded, the connection proceeds with no token and noauthEndpointcall at all. - If it is guarded, the SDK calls
authEndpointand uses theauthfield from the response as the token.
forceAuth is a client-side option, it's how you make your own SDK instance always authenticate, even for channels that aren't private-. It's independent from the dashboard's account-wide forced authentication above, that one is enforced by the server for every connection to your API key regardless of what any client does; forceAuth only changes what this one SDK instance does on its own.
Your endpoint just needs to respond with:
{ "auth": "JWT_TOKEN" }
The request your endpoint receives differs slightly by platform:
- JavaScript sends a
multipart/form-dataPOST with achannel_namefield, cookies included. If you're on Laravel, see PieSocket + Laravel, the default broadcasting auth route just works with no changes. - Android, Flutter, and Swift send a JSON body instead:
{ "channel_name": "...", "connection_uuid": "..." }, withContent-Type: application/json.
Either way, your endpoint should check that the current user is allowed to join channel_name and respond with a JWT signed the same way as manual generation below, with sub set to channel_name. Add custom headers (an auth token, a CSRF header) with authHeaders.
Manual: pass a JWT directly
| Method | Key | Example |
|---|---|---|
| Query param | jwt | wss://demo.piesocket.com/v4/CHANNEL_ID?api_key=xxx&jwt=JWT_TOKEN |
| Header | jwt | jwt: JWT_TOKEN |
Generate the token on your own server using your API secret, HS256, and this payload:
{
"sub": CHANNEL_ID,
"iat": ISSUED_TIME,
"exp": EXPIRY_TIME (OPTIONAL)
"user": NUMBER|STRING|JSON (OPTIONAL)
}
The token is only valid for the channel named in sub, generate a separate one per channel. You can generate one for testing with our JWT Encoder.
You can sign this payload in any language with a standard JWT library:
use Firebase\JWT\JWT;
$payload = [
'sub' => 'CHANNEL_ID',
'iat' => time(),
'exp' => time() + 3600,
'user' => $userId,
];
$token = JWT::encode($payload, 'YOUR_API_SECRET', 'HS256');
import jwt
import time
payload = {
"sub": "CHANNEL_ID",
"iat": int(time.time()),
"exp": int(time.time()) + 3600,
"user": user_id,
}
token = jwt.encode(payload, "YOUR_API_SECRET", algorithm="HS256")
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{ sub: 'CHANNEL_ID', user: userId },
'YOUR_API_SECRET',
{ algorithm: 'HS256', expiresIn: '1h' }
);
import (
"time"
"github.com/dgrijalva/jwt-go"
)
claims := jwt.MapClaims{
"sub": "CHANNEL_ID",
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour).Unix(),
"user": userId,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString([]byte("YOUR_API_SECRET"))
require 'jwt'
payload = {
sub: 'CHANNEL_ID',
iat: Time.now.to_i,
exp: Time.now.to_i + 3600,
user: user_id
}
token = JWT.encode(payload, 'YOUR_API_SECRET', 'HS256')
Libraries used above: firebase/php-jwt, PyJWT, jsonwebtoken, jwt-go, ruby-jwt.
Once your backend hands the client this token (however you deliver it, an API response, embedded in the page, etc.), pass it as the jwt option when connecting. This skips the authEndpoint call entirely, see the precedence order above:
const piesocket = new PieSocket({
version: 4,
clusterId: 'YOUR_CLUSTER_ID',
apiKey: 'YOUR_API_KEY',
jwt: token,
});
PieSocketOptions options = new PieSocketOptions();
options.setVersion("4");
options.setClusterId("YOUR_CLUSTER_ID");
options.setApiKey("YOUR_API_KEY");
options.setJwt(token);
PieSocketOptions options = PieSocketOptions();
options.setVersion("4");
options.setClusterId("YOUR_CLUSTER_ID");
options.setApiKey("YOUR_API_KEY");
options.setJwt(token);
let options = PieSocketOptions()
options.setVersion(version: "4")
options.setClusterId(clusterId: "YOUR_CLUSTER_ID")
options.setApiKey(apiKey: "YOUR_API_KEY")
options.setJwt(jwt: token)
Not using an official SDK? Pass it directly on the connection instead, see the query param or header table earlier in this section.
Identifying a user
There are two ways to set a connection's user identity:
- Unsecured: pass
&user=idon the connection URL. Simple, but anyone who can reach your API key can claim any identity. - Secure: put the user id/name/JSON into the JWT payload's
userclaim, as described above.
If you want to notify one specific user, use a channel name derived from their ID, for example, every user joins a channel named user-123 where 123 is their own ID. To notify user 456, publish to user-456. Since that requires a JWT, other users can't join it without your server issuing them one.
