Realtime Authentication¶
Learn how to authenticate realtime connections using bearer tokens or service account credentials.
Overview¶
Realtime connections require authentication to ensure only authorized users receive events. The SDK handles token management automatically, but understanding the flow helps with debugging and advanced use cases.
Authentication Methods¶
1. User Token (Browser Applications)¶
For browser applications where users are logged in:
import { CentraliSDK } from '@centrali-io/centrali-sdk';
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-workspace',
token: userJwtToken // From your auth flow
});
// Token is automatically used for realtime
centrali.realtime.subscribe({
onEvent: handleEvent
});
Updating Tokens¶
If your token expires and you get a new one:
// Update token for subsequent requests
centrali.setToken(newToken);
// Realtime connections will use the new token on reconnect
2. Service Account (Server-Side Applications)¶
For server-to-server communication using client credentials:
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-workspace',
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
// Token is automatically fetched on first use
centrali.realtime.subscribe({
onEvent: handleEvent
});
The SDK automatically: - Fetches an access token using client credentials on first connection - Uses the token for the realtime connection
Important: The SDK caches the token and does NOT automatically refresh it when it expires. For long-running server applications, you should handle token expiry:
centrali.realtime.subscribe({
onEvent: handleEvent,
onError: async (error) => {
if (error.code === 'TOKEN_EXPIRED') {
// Manually refresh the token
const newToken = await centrali.fetchServiceAccountToken();
centrali.setToken(newToken);
// SDK will reconnect with new token
}
}
});
Token Requirements¶
Workspace Scope¶
The token must be valid for the workspace you're connecting to. Tokens issued for workspace A cannot access realtime events for workspace B.
Required Permissions¶
The user or service account must have the realtime:subscribe permission for the workspace. This is typically included in standard user roles.
How Authentication Works¶
Connection Flow¶
- SDK requests a connection to
/realtime/workspace/{ws}/events - Token is passed as
access_tokenquery parameter (SSE limitation) - Server validates the JWT against JWKS endpoint
- Server checks workspace claim matches the URL
- Server verifies IAM permissions via internal RPC
- Connection is established or error returned
Token Delivery¶
Since SSE connections cannot use custom headers in browsers, tokens are passed via query parameter:
The SDK handles this automatically. For raw HTTP access, see the API Reference.
Error Handling¶
Authentication Errors¶
The SDK provides specific error codes for auth failures:
centrali.realtime.subscribe({
onEvent: handleEvent,
onError: (error) => {
switch (error.code) {
case 'MISSING_TOKEN':
// No token provided - redirect to login
redirectToLogin();
break;
case 'TOKEN_EXPIRED':
// Token expired - refresh and retry
refreshToken().then(() => reconnect());
break;
case 'INVALID_TOKEN':
// Token is malformed or signature invalid
console.error('Invalid token:', error.message);
break;
case 'WORKSPACE_MISMATCH':
// Token is for a different workspace
console.error('Token not valid for this workspace');
break;
case 'FORBIDDEN':
// User doesn't have realtime permissions
console.error('Permission denied');
break;
}
}
});
Recoverable vs Non-Recoverable Errors¶
| Error Code | Recoverable | Action |
|---|---|---|
MISSING_TOKEN | No | Redirect to login |
TOKEN_EXPIRED | Yes | Refresh token, SDK will retry |
INVALID_TOKEN | No | Re-authenticate user |
WORKSPACE_MISMATCH | No | Use correct workspace |
FORBIDDEN | No | Request access |
AUTH_ERROR | Yes | SDK will retry |
Token Refresh Strategies¶
Browser Applications¶
Integrate with your auth provider's refresh mechanism:
import { useAuth } from 'your-auth-provider';
function MyComponent() {
const { token, refreshToken } = useAuth();
const centraliRef = useRef<CentraliSDK>();
useEffect(() => {
centraliRef.current = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-workspace',
token
});
const subscription = centraliRef.current.realtime.subscribe({
onEvent: handleEvent,
onError: async (error) => {
if (error.code === 'TOKEN_EXPIRED') {
const newToken = await refreshToken();
centraliRef.current?.setToken(newToken);
// Reconnection will use new token
}
}
});
return () => subscription.unsubscribe();
}, []);
// Update token when it changes
useEffect(() => {
centraliRef.current?.setToken(token);
}, [token]);
}
Service Accounts¶
The SDK does not automatically refresh service account tokens. For long-running applications, handle token expiry in your error callback:
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-workspace',
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
centrali.realtime.subscribe({
onEvent: handleEvent,
onError: async (error) => {
if (error.code === 'TOKEN_EXPIRED') {
// Fetch a new token and update the SDK
const newToken = await centrali.fetchServiceAccountToken();
centrali.setToken(newToken);
// The SDK will automatically reconnect with the new token
}
}
});
Security Considerations¶
Token in URL¶
SSE requires passing the token in the URL query string. This means:
- Tokens may appear in server logs - Configure your load balancer to scrub
access_tokenparameters - Tokens visible in browser history - Not a concern for SSE connections (not navigated URLs)
- Use short-lived tokens - Reduces exposure window
Recommendations¶
- Use short token expiry (15-60 minutes) for browser tokens
- Rotate service account credentials periodically
- Monitor for unauthorized access using IAM audit logs
- Use HTTPS only - Never use realtime over HTTP
Testing Authentication¶
Verify Token Locally¶
// Decode JWT to check claims (don't use in production)
function decodeToken(token: string) {
const payload = token.split('.')[1];
return JSON.parse(atob(payload));
}
const claims = decodeToken(yourToken);
console.log('User ID:', claims.sub);
console.log('Workspace:', claims.workspaces);
console.log('Expires:', new Date(claims.exp * 1000));
Test Connection¶
const subscription = centrali.realtime.subscribe({
onConnected: () => {
console.log('Authentication successful!');
},
onError: (error) => {
console.error('Auth failed:', error.code, error.message);
},
onEvent: () => {} // Required
});
Related Documentation¶
- Quickstart - Get started with realtime
- Service Account Authentication - Setting up service accounts
- Authentication Overview - General auth concepts