Authentication API
Secure authentication with JWT tokens, session management, and Clerk integration for user management
Quick Navigation
JWT Security
Industry-standard JWT tokens with RS256 algorithm and Clerk integration
Session Management
Long-lived session tokens with automatic refresh and machine binding
Machine Authorization
Machine-specific authentication for service-to-service communication
Authentication Flow
Playcast uses a two-tier authentication system with JWT tokens and session management.
- JWT Token - Initial authentication with Clerk
- Session Token - Long-lived machine-specific sessions
Quick Start
Get started with Playcast authentication:
// For first-time authentication with JWT
const authResponse = await fetch('/realtime/auth/login-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machineId: 'machine-123',
jwt: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
})
});
const { loginToken, sessionToken, expiresIn } = await authResponse.json();
// For subsequent requests with session token
const quickAuth = await fetch('/realtime/auth/login-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machineId: 'machine-123',
sessionToken: sessionToken
})
});
// Store session token for future use
localStorage.setItem('playcast_session', sessionToken);
Login Token
Request a Clerk sign-in token for authentication.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| machineId | string | Required | Unique identifier for the requesting machine |
| sessionToken | string | Optional | Existing session token for re-authentication |
| jwt | string | Optional | Clerk JWT token for initial authentication |
Response
Success Response (200)
{
"success": true,
"loginToken": "situ_2b1234567890abcdef",
"sessionToken": "sess_1a234567890bcdef",
"expiresIn": 1800,
"sessionExpiresIn": 86400
}
Error Response (401)
{
"success": false,
"error": "Authentication failed - valid session token or JWT required"
}
Usage Examples
First-time Authentication with JWT
async function authenticateWithJWT(jwtToken, machineId) {
try {
const response = await fetch('/realtime/auth/login-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machineId: machineId,
jwt: jwtToken
})
});
const result = await response.json();
if (result.success) {
// Store session token for future use
localStorage.setItem('playcast_session', result.sessionToken);
localStorage.setItem('playcast_session_expires',
Date.now() + (result.sessionExpiresIn * 1000));
return result.loginToken;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Authentication failed:', error);
throw error;
}
}
Re-authentication with Session Token
async function authenticateWithSession(machineId) {
const sessionToken = localStorage.getItem('playcast_session');
const sessionExpires = localStorage.getItem('playcast_session_expires');
if (!sessionToken || Date.now() > parseInt(sessionExpires)) {
throw new Error('Session token expired or missing');
}
try {
const response = await fetch('/realtime/auth/login-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machineId: machineId,
sessionToken: sessionToken
})
});
const result = await response.json();
if (result.success) {
return result.loginToken;
} else {
// Session might be expired, clear it
localStorage.removeItem('playcast_session');
localStorage.removeItem('playcast_session_expires');
throw new Error(result.error);
}
} catch (error) {
console.error('Session authentication failed:', error);
throw error;
}
}
Token Validation
Validate a session token without requesting a new login token.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionToken | string | Required | Session token to validate |
| machineId | string | Required | Machine ID that session token is bound to |
Response
Valid Token Response (200)
{
"success": true,
"valid": true,
"authId": "user_2b1234567890abcdef"
}
Invalid Token Response (200)
{
"success": true,
"valid": false
}
Usage Example
async function validateSession(sessionToken, machineId) {
try {
const response = await fetch('/realtime/auth/validate-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionToken: sessionToken,
machineId: machineId
})
});
const result = await response.json();
if (result.success && result.valid) {
console.log('Session is valid for user:', result.authId);
return true;
} else {
console.log('Session is invalid or expired');
return false;
}
} catch (error) {
console.error('Token validation failed:', error);
return false;
}
}
// Use in your application
const isValid = await validateSession(
localStorage.getItem('playcast_session'),
'machine-123'
);
if (!isValid) {
// Redirect to login or request new authentication
redirectToLogin();
}
Session Refresh
Refresh an existing session token to extend its validity period.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionToken | string | Required | Current session token to refresh |
| machineId | string | Required | Machine ID bound to the session |
Response
Success Response (200)
{
"success": true,
"sessionToken": "sess_2c345678901cdefg",
"expiresIn": 86400
}
Error Response (401)
{
"success": false,
"error": "Invalid or expired session token"
}
Usage Example
async function refreshSessionToken(sessionToken, machineId) {
try {
const response = await fetch('/realtime/auth/refresh-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionToken: sessionToken,
machineId: machineId
})
});
const result = await response.json();
if (result.success) {
// Update stored session token
localStorage.setItem('playcast_session', result.sessionToken);
localStorage.setItem('playcast_session_expires',
Date.now() + (result.expiresIn * 1000));
console.log('Session refreshed successfully');
return result.sessionToken;
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Session refresh failed:', error);
// Clear invalid session
localStorage.removeItem('playcast_session');
localStorage.removeItem('playcast_session_expires');
throw error;
}
}
// Automatic refresh before expiration
function scheduleSessionRefresh() {
const sessionExpires = localStorage.getItem('playcast_session_expires');
if (!sessionExpires) return;
const expirationTime = parseInt(sessionExpires);
const refreshTime = expirationTime - (30 * 60 * 1000); // 30 minutes before expiry
const timeUntilRefresh = refreshTime - Date.now();
if (timeUntilRefresh > 0) {
setTimeout(async () => {
try {
const sessionToken = localStorage.getItem('playcast_session');
await refreshSessionToken(sessionToken, 'machine-123');
scheduleSessionRefresh(); // Schedule next refresh
} catch (error) {
console.error('Automatic refresh failed:', error);
}
}, timeUntilRefresh);
}
}
Error Handling
Handle authentication errors gracefully in your application.
Common Error Scenarios
class PlaycastAuth {
constructor(machineId) {
this.machineId = machineId;
this.baseUrl = '/realtime/auth';
}
async authenticate() {
try {
// Try session token first
const sessionToken = localStorage.getItem('playcast_session');
if (sessionToken && !this.isSessionExpired()) {
return await this.authenticateWithSession(sessionToken);
}
// Fall back to JWT if available
const jwtToken = this.getStoredJWT();
if (jwtToken) {
return await this.authenticateWithJWT(jwtToken);
}
// No authentication available
throw new Error('No authentication credentials available');
} catch (error) {
return this.handleAuthError(error);
}
}
async authenticateWithSession(sessionToken) {
const response = await fetch(`${this.baseUrl}/login-token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machineId: this.machineId,
sessionToken: sessionToken
})
});
const result = await response.json();
if (!result.success) {
throw new AuthError(result.error, 'SESSION_INVALID');
}
return result.loginToken;
}
async authenticateWithJWT(jwtToken) {
const response = await fetch(`${this.baseUrl}/login-token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machineId: this.machineId,
jwt: jwtToken
})
});
const result = await response.json();
if (!result.success) {
throw new AuthError(result.error, 'JWT_INVALID');
}
// Store new session token
if (result.sessionToken) {
localStorage.setItem('playcast_session', result.sessionToken);
localStorage.setItem('playcast_session_expires',
Date.now() + (result.sessionExpiresIn * 1000));
}
return result.loginToken;
}
handleAuthError(error) {
if (error instanceof AuthError) {
switch (error.code) {
case 'SESSION_INVALID':
// Clear invalid session and retry with JWT
localStorage.removeItem('playcast_session');
localStorage.removeItem('playcast_session_expires');
return this.authenticateWithJWT(this.getStoredJWT());
case 'JWT_INVALID':
// JWT expired or invalid, need user re-authentication
this.redirectToLogin();
throw new Error('Please log in again');
default:
console.error('Authentication error:', error.message);
throw error;
}
}
// Network or other errors
console.error('Authentication request failed:', error);
throw new Error('Authentication service unavailable');
}
isSessionExpired() {
const expires = localStorage.getItem('playcast_session_expires');
return !expires || Date.now() > parseInt(expires);
}
getStoredJWT() {
// Implement based on your JWT storage strategy
return localStorage.getItem('clerk_jwt');
}
redirectToLogin() {
// Implement based on your application's login flow
window.location.href = '/login';
}
}
class AuthError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = 'AuthError';
}
}
Retry Logic with Exponential Backoff
async function authenticateWithRetry(auth, maxRetries = 3) {
let retryCount = 0;
while (retryCount < maxRetries) {
try {
return await auth.authenticate();
} catch (error) {
retryCount++;
if (retryCount >= maxRetries) {
throw new Error(`Authentication failed after ${maxRetries} attempts: ${error.message}`);
}
// Exponential backoff
const delay = Math.pow(2, retryCount) * 1000;
console.log(`Authentication attempt ${retryCount} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
- Always validate tokens server-side before trusting client claims
- Use HTTPS in production to protect tokens in transit
- Implement proper token storage (HttpOnly cookies when possible)
- Set reasonable session expiration times
- Log authentication failures for security monitoring
Integration with Clerk
Playcast authentication integrates with Clerk for user management and JWT token verification.
Environment Setup
# Production environment
CLERK_PROD_SECRET_KEY=sk_prod_xxxxxxxxxxxxxxxxxxxx
# Development environment
CLERK_DEVELOPMENT_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxx
# Clerk public keys are embedded in the API for JWT verification
JWT Token Format
// Example JWT payload from Clerk
{
"sub": "user_2b1234567890abcdef", // User ID
"iss": "https://clerk.domain.com", // Issuer
"aud": "your-app-id", // Audience
"exp": 1640995200, // Expiration timestamp
"iat": 1640991600, // Issued at timestamp
"nbf": 1640991600 // Not before timestamp
}
- Industry-standard security practices
- Multiple authentication providers
- User management dashboard
- Automatic security updates
- Compliance with privacy regulations