Secure PubNub applications with Access Manager, encryption, and TLS
// SERVER-SIDE ONLY
const pubnub = new PubNub({
publishKey: 'pub-c-...',
subscribeKey: 'sub-c-...',
secretKey: 'sec-c-...', // ONLY on server
userId: 'server'
});// CLIENT-SIDE - No Secret Key
const pubnub = new PubNub({
publishKey: 'pub-c-...',
subscribeKey: 'sub-c-...',
userId: 'user-123',
authKey: 'token-from-server'
// NO secretKey here
});// Use environment variables
const pubnub = new PubNub({
publishKey: process.env.PUBNUB_PUB_KEY,
subscribeKey: process.env.PUBNUB_SUB_KEY,
secretKey: process.env.PUBNUB_SECRET_KEY, // Server only
userId: 'server'
});Production:
- pub-c-prod-...
- sub-c-prod-...
- sec-c-prod-...
Staging:
- pub-c-staging-...
- sub-c-staging-...
- sec-c-staging-...
Development:
- pub-c-dev-...
- sub-c-dev-...
- sec-c-dev-...// Grant minimum required permissions
await pubnub.grant({
channels: ['user-feed'],
authKeys: [userAuthKey],
read: true, // Can subscribe
write: false, // Cannot publish
ttl: 60
});
// Only grant write for specific channels
await pubnub.grant({
channels: ['user-posts'],
authKeys: [userAuthKey],
read: false,
write: true,
ttl: 60
});// Design channels with security boundaries
// Public - no auth required (if PAM disabled)
'public-announcements'
// Private user channel
'private-user-${userId}'
// Group with membership check
'group-${groupId}' // Grant only to members
// Admin-only
'admin-dashboard' // Grant only to admin roleasync function grantRoleAccess(userId, role, authKey) {
const rolePermissions = {
viewer: {
channels: ['public-*'],
read: true,
write: false
},
member: {
channels: ['public-*', 'chat-*'],
read: true,
write: true
},
moderator: {
channels: ['public-*', 'chat-*', 'mod-*'],
read: true,
write: true,
manage: true
},
admin: {
channels: ['*'],
read: true,
write: true,
manage: true
}
};
const perms = rolePermissions[role];
await pubnub.grant({
channels: perms.channels,
authKeys: [authKey],
read: perms.read,
write: perms.write,
manage: perms.manage || false,
ttl: 1440 // 24 hours
});
}// Sensitive operations: short TTL
await pubnub.grant({
channels: ['financial-data'],
authKeys: [authKey],
read: true,
ttl: 15 // 15 minutes
});
// Regular operations: moderate TTL
await pubnub.grant({
channels: ['chat-room'],
authKeys: [authKey],
read: true,
write: true,
ttl: 60 // 1 hour
});// Client-side token refresh
class PubNubAuthManager {
constructor() {
this.refreshBuffer = 5 * 60 * 1000; // 5 minutes before expiry
}
async initialize() {
const credentials = await this.fetchCredentials();
this.setupPubNub(credentials);
this.scheduleRefresh(credentials.expiresAt);
}
async fetchCredentials() {
const response = await fetch('/api/pubnub/auth', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`
}
});
return response.json();
}
setupPubNub(credentials) {
this.pubnub = new PubNub({
subscribeKey: credentials.subscribeKey,
publishKey: credentials.publishKey,
userId: credentials.userId,
authKey: credentials.authKey
});
}
scheduleRefresh(expiresAt) {
const refreshTime = expiresAt - Date.now() - this.refreshBuffer;
setTimeout(() => this.refresh(), Math.max(refreshTime, 0));
}
async refresh() {
const credentials = await this.fetchCredentials();
this.pubnub.setAuthKey(credentials.authKey);
this.scheduleRefresh(credentials.expiresAt);
}
}pubnub.addListener({
status: (statusEvent) => {
switch (statusEvent.category) {
case 'PNAccessDeniedCategory':
console.error('Access denied');
console.error('Channels:', statusEvent.affectedChannels);
// Option 1: Refresh token and retry
refreshAuthToken().then(() => {
pubnub.subscribe({
channels: statusEvent.affectedChannels
});
});
// Option 2: Redirect to login
// window.location.href = '/login';
// Option 3: Show error to user
// showError('Session expired. Please log in again.');
break;
}
}
});// Include user context in private channels
`dm-${sortedUserIds.join('-')}`
// Include ownership in channels
`user-${ownerId}-posts`
// Use prefixes for different security levels
`public-announcements`
`private-user-123`
`secret-admin-channel`// PubNub Function: Validate before publish
export default async (request) => {
const message = request.message;
const channel = request.channels[0];
// Validate sender has permission for this channel
if (channel.startsWith('user-')) {
const channelUserId = channel.split('-')[1];
if (message.senderId !== channelUserId) {
console.error('Unauthorized publish attempt');
return request.abort();
}
}
// Validate message structure
if (!message.senderId || !message.content) {
return request.abort();
}
return request.ok();
};// Generate cryptographically secure key
const crypto = require('crypto');
const cipherKey = crypto.randomBytes(32).toString('hex');// Different cipher keys for different data sensitivity
const publicPubnub = new PubNub({
subscribeKey: 'sub-c-...',
userId: 'user-123'
// No encryption for public channels
});
const privatePubnub = new PubNub({
subscribeKey: 'sub-c-...',
userId: 'user-123',
cipherKey: 'private-conversations-key'
});
const financialPubnub = new PubNub({
subscribeKey: 'sub-c-...',
userId: 'user-123',
cipherKey: 'financial-data-key' // Different key for sensitive data
});// Use Message Persistence for audit logs
await pubnub.publish({
channel: 'audit-log',
message: {
action: 'user_login',
userId: user.id,
timestamp: new Date().toISOString(),
ip: request.ip,
userAgent: request.headers['user-agent']
}
});
// Messages stored for configured retention periodauthKey (not token) in client configurationtessl i pubnub/pubnub-security@0.1.4