Complete guide for integrating RegardingWork services with Hub authentication
/api/auth/validate
endpointyourapp.com/api/auth/validate
Agents instinctively try to build OAuth servers!
When asked to "add authentication," agents default to building backend auth systems instead of using Hub's APIs.
Solution: Always specify "Use Hub Mini-App pattern - no backend auth code!"
hub.regardingwork.com/api/auth/validate
URL: https://ssotest.regardingwork.com
Purpose: Reference implementation showing working Hub SSO integration
Test Account: Login as "michelini" on Hub for full testing
Hub Enhancement: Hub now sends both token formats for maximum compatibility!
/sso/callback?token=eyJ0eXAi... &access_token=eyJ0eXAi... &user_id=123 &username=michelini
// Legacy pattern (Teams, etc.) const token = urlParams.get('token'); // Modern pattern (Templates, etc.) const token = urlParams.get('access_token'); // Or both for max compatibility!
Benefit: This update fixed SSO integration issues and ensures all new templates work immediately without breaking existing apps.
Backward Compatibility Guaranteed: Hub API responses now support both old and new formats!
// Original integrations (EPD, Teams, etc.) const username = response.username; const userId = response.id; const email = response.email;
// New integrations can use either! const username = response.user.username; const userId = response.user.id; const email = response.user.email;
Problem Solved: client.easyprodesign.com was getting "server_error" redirects because their code expected response.username
but Hub was only sending response.user.username
.
Solution Implemented: Hub now sends BOTH formats in every validation response, ensuring 100% backward compatibility. EPD integration now works perfectly without any code changes!
Promise: All existing integrations will continue working without changes. No more breaking API updates!
Use this test site as your reference when building SSO integrations. The source code demonstrates the exact patterns needed for successful Hub authentication.
Troubleshooting: If your integration fails, compare your implementation with this working example.
Teams agent successfully implemented this pattern and eliminated all infinite login loops! Follow these exact steps.
โ
Users click login โ redirected to Hub โ return to your service dashboard
โ
No "Signature verification failed" errors
โ
No infinite redirect loops
response.username
instead of response.user.username
response.user.username
/api/auth/sso/authorize
endpoint
/login
endpoint instead of SSO/api/auth/sso/authorize
flow
Authorization: Bearer {token}
with GET
RegardingWork Hub serves as the central authentication service for the entire RegardingWork ecosystem. This guide explains how to integrate your RegardingWork service with Hub for Single Sign-On (SSO) functionality.
This guide is specifically designed for services within the RegardingWork ecosystem. External integrations should use OAuth 2.0 flows instead.
This exact code eliminated all authentication issues for Teams. Copy and adapt for your service.
// === TEAMS SUCCESSFUL IMPLEMENTATION ===
// Replace 'teams' with your service name (e.g., 'game', 'premium')
// 1. LOGIN BUTTON - Replace your existing login button
function startSSO() {
const redirectUri = encodeURIComponent('https://teams.regardingwork.com/sso/callback');
const ssoUrl = `https://hub.regardingwork.com/api/auth/sso/authorize?redirect_uri=${redirectUri}&service=teams`;
console.log('Starting SSO with URL:', ssoUrl);
window.location.href = ssoUrl;
}
// 2. SSO CALLBACK HANDLER - Add this to handle Hub's redirect
// Put this code in your main JavaScript file or in /sso/callback route
if (window.location.pathname === '/sso/callback') {
console.log('=== SSO CALLBACK PROCESSING ===');
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token'); // โ
Correct parameter name
const userId = urlParams.get('user_id');
const username = urlParams.get('username');
console.log('Token received:', !!token);
console.log('User ID:', userId);
console.log('Username:', username);
if (token && userId && username) {
// Success - store credentials
localStorage.setItem('hub_jwt_token', token);
localStorage.setItem('hub_user_data', JSON.stringify({
id: userId,
username: username
}));
console.log('โ
SSO success - redirecting to dashboard');
window.location.href = '/dashboard';
} else {
console.error('โ SSO failed - missing parameters');
alert('Login failed. Please try again.');
window.location.href = '/login';
}
}
// 3. USER VALIDATION - Call Hub directly (no backend needed)
async function validateUser() {
const token = localStorage.getItem('hub_jwt_token');
if (!token) {
console.log('No token found - starting SSO');
startSSO();
return null;
}
try {
const response = await fetch('https://hub.regardingwork.com/api/auth/validate', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const data = await response.json();
console.log('โ
User validated:', data.user);
return data.user; // โ
Use data.user.username, data.user.email
} else {
console.log('โ Token invalid - restarting SSO');
localStorage.removeItem('hub_jwt_token');
startSSO();
return null;
}
} catch (error) {
console.error('Validation failed:', error);
startSSO();
return null;
}
}
// 4. LOGOUT FUNCTION - Global logout across all services
function logout() {
localStorage.removeItem('hub_jwt_token');
localStorage.removeItem('hub_user_data');
// Redirect to Hub's global logout
const redirectAfterLogout = encodeURIComponent('https://teams.regardingwork.com/login');
window.location.href = `https://hub.regardingwork.com/auth/global-logout?redirect=${redirectAfterLogout}`;
}
// 5. INITIALIZATION - Add this to your main page load
document.addEventListener('DOMContentLoaded', async function() {
// Skip validation on login and callback pages
if (window.location.pathname === '/login' || window.location.pathname === '/sso/callback') {
return;
}
const user = await validateUser();
if (user) {
console.log('User authenticated:', user.username);
// Update UI with user info
document.getElementById('username').textContent = user.username;
}
});
// === HTML INTEGRATION ===
// Replace your login button with:
// <button onclick="startSSO()">Login with RegardingWork</button>
// Add logout button:
// <button onclick="logout()">Logout</button>
// === AUTHENTICATION DEBUG SCRIPT ===
// Copy this entire script to your browser console for debugging
console.log('=== AUTHENTICATION DEBUG INFO ===');
console.log('Current URL:', window.location.href);
console.log('Pathname:', window.location.pathname);
console.log('Search params:', window.location.search);
// Check stored tokens
const token = localStorage.getItem('hub_jwt_token');
const userData = localStorage.getItem('hub_user_data');
console.log('Token exists:', !!token);
console.log('Token preview:', token ? token.substring(0, 50) + '...' : 'None');
console.log('User data:', userData);
// Check URL parameters (for SSO callback debugging)
const urlParams = new URLSearchParams(window.location.search);
console.log('URL Parameters:');
console.log('- token:', urlParams.get('token'));
console.log('- user_id:', urlParams.get('user_id'));
console.log('- username:', urlParams.get('username'));
console.log('- sso_token:', urlParams.get('sso_token')); // Old parameter
console.log('- user:', urlParams.get('user')); // Old parameter
// Test Hub validation
if (token) {
console.log('Testing token validation...');
fetch('https://hub.regardingwork.com/api/auth/validate', {
headers: { 'Authorization': `Bearer ${token}` }
})
.then(response => {
console.log('Validation response status:', response.status);
return response.json();
})
.then(data => {
console.log('Validation response:', data);
if (data.user) {
console.log('โ
Token valid - User:', data.user.username);
} else {
console.log('โ Token validation failed');
}
})
.catch(error => {
console.error('โ Validation error:', error);
});
} else {
console.log('No token to validate');
}
// Check CORS configuration
console.log('Origin:', window.location.origin);
console.log('Expected CORS domains:', [
'https://teams.regardingwork.com',
'https://game.regardingwork.com',
'https://premium.regardingwork.com',
'https://display.regardingwork.com'
]);
console.log('=== END DEBUG INFO ===');
Just want it to work? Copy this code and replace YOUR_DOMAIN with your actual domain:
// 1. Handle SSO callback (put this in your login page)
function handleSSO() {
const urlParams = new URLSearchParams(window.location.search);
const ssoToken = urlParams.get('sso_token');
const userDataStr = urlParams.get('user');
if (ssoToken && userDataStr) {
localStorage.setItem('hub_jwt_token', ssoToken);
localStorage.setItem('hub_user_data', userDataStr);
window.location.href = '/dashboard';
}
}
// 2. Check if user is logged in (put this everywhere)
function checkAuth() {
const token = localStorage.getItem('hub_jwt_token');
if (!token) {
window.location.href = `https://hub.regardingwork.com/login?redirect=${encodeURIComponent(window.location.href)}`;
return false;
}
return true;
}
// 3. Validate token (optional - only if you need fresh user data)
async function validateUser() {
const token = localStorage.getItem('hub_jwt_token');
const response = await fetch('https://hub.regardingwork.com/api/auth/validate', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const data = await response.json();
return data.user; // Use data.user.username, data.user.email, etc.
} else {
checkAuth(); // Redirect to login
return null;
}
}
// 4. Call on page load
document.addEventListener('DOMContentLoaded', function() {
handleSSO(); // Check for SSO callback
checkAuth(); // Ensure user is logged in
});
Request your domain to be added via SSO Access Request, then deploy this code.
Add robust error handling for network failures and API issues:
// Robust fetch with retry logic and specific error handling
async function fetchWithRetry(url, options, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
// Handle specific HTTP status codes
if (!response.ok) {
if (response.status === 401) {
throw new Error('Invalid or expired token - redirecting to login');
}
if (response.status === 403) {
throw new Error('Unauthorized access - insufficient permissions');
}
if (response.status === 404) {
throw new Error('Endpoint not found - check Hub URL');
}
if (response.status >= 500) {
throw new Error(`Hub server error: ${response.status}`);
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
} catch (error) {
console.log(`Attempt ${i + 1} failed:`, error.message);
// Don't retry on authentication errors
if (error.message.includes('401') || error.message.includes('403')) {
throw error;
}
// Retry on network/server errors
if (i === retries - 1) throw error;
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
}
// Enhanced validation with error handling
async function validateUserWithRetry() {
const token = localStorage.getItem('hub_jwt_token');
if (!token) {
console.log('No token found - redirecting to login');
redirectToLogin();
return null;
}
try {
const data = await fetchWithRetry('https://hub.regardingwork.com/api/auth/validate', {
headers: { 'Authorization': `Bearer ${token}` }
});
console.log('โ
User validated:', data.user);
return data.user;
} catch (error) {
console.error('โ Validation failed:', error.message);
if (error.message.includes('Invalid or expired token')) {
localStorage.removeItem('hub_jwt_token');
redirectToLogin();
}
return null;
}
}
Choose the integration pattern that best fits your service's needs:
Use when: Your service has backend APIs that need user data and authentication
Services using this: Game app, Premium app
Features:
Use when: Your service just needs to know if user is logged in
Services using this: Display app, simple frontends
Features:
In Your Replit Service:
JWT_SECRET_KEY
In Your Code:
import os
import jwt
# Environment configuration
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY')
JWT_ALGORITHM = 'HS256'
Add this validation function to your service:
def validate_hub_jwt(token):
"""Validate JWT token from RegardingWork Hub"""
try:
payload = jwt.decode(
token,
JWT_SECRET_KEY,
algorithms=[JWT_ALGORITHM]
)
# Extract user info from Hub token
return {
'valid': True,
'user_id': payload.get('user_id'),
'username': payload.get('username'),
'email': payload.get('email')
}
except jwt.ExpiredSignatureError:
return {'valid': False, 'error': 'Token expired'}
except jwt.InvalidTokenError:
return {'valid': False, 'error': 'Invalid token'}
Before validation, clients need to obtain tokens. Here are the common flows:
// 1. Client initiates SSO
const ssoUrl = `https://hub.regardingwork.com/api/auth/sso/authorize?redirect_uri=${encodeURIComponent('https://yourapp.com/api/auth/callback')}&service=yourapp`;
window.location.href = ssoUrl;
// 2. Hub redirects to your callback with token
// GET /api/auth/callback?token=eyJ0eXAi...
// 3. Your callback stores token and redirects
app.get('/api/auth/callback', (req, res) => {
const token = req.query.token;
res.redirect(`/dashboard?token=${token}`);
});
// 4. Frontend stores token
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
if (token) {
localStorage.setItem('hub_jwt_token', token);
}
// Alternative: Direct login API call
async function loginUser(username, password) {
const response = await fetch('https://hub.regardingwork.com/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (response.ok) {
const data = await response.json();
localStorage.setItem('hub_jwt_token', data.access_token);
localStorage.setItem('hub_refresh_token', data.refresh_token);
return data.user;
}
throw new Error('Login failed');
}
All endpoints mini-apps interact with for authentication:
POST /api/auth/login
Input: { "username": "", "password": "" }
Output: {
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"user": {
"id": 123,
"username": "johndoe",
"email": "john@example.com"
}
}
Status: 200 (success), 401 (invalid credentials)
POST /api/auth/refresh
Input: { "refresh_token": "eyJ..." }
Output: { "access_token": "eyJ..." }
Status: 200 (success), 401 (invalid refresh token)
GET /api/auth/validate
Headers: Authorization: Bearer eyJ...
Output: {
"valid": true,
"user": { "id": 123, "username": "johndoe" },
"expires_at": 1640995200
}
Status: 200 (valid), 401 (invalid/expired)
GET /api/auth/sso/authorize
Params:
redirect_uri=https://yourapp.com/callback
service=yourapp
Response: Redirects to redirect_uri with ?token=eyJ...
GET /logout
Params: redirect=https://yourapp.com/login
Response: Clears session, redirects to app
Status: 302 (redirect)
POST /api/auth/logout
Headers: Authorization: Bearer eyJ...
Input: { "refresh_token": "eyJ..." } (optional)
Output: { "message": "Logout successful" }
Status: 200 (success), 401 (invalid token)
from functools import wraps
from flask import request, jsonify
def require_hub_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({'error': 'Authorization header required'}), 401
token = auth_header.split(' ')[1]
validation = validate_hub_jwt(token)
if not validation['valid']:
return jsonify({'error': validation['error']}), 401
# Add user info to request context
request.current_user = validation
return f(*args, **kwargs)
return decorated_function
# Usage example
@app.route('/api/user-data')
@require_hub_auth
def get_user_data():
user = request.current_user
return jsonify({
'message': f'Hello {user["username"]}!',
'user_data': user
})
This is the simple pattern used by Teams, Family, Desk, and other RegardingWork services. Much easier than OAuth!
CRITICAL: Hub sends different parameters than documented elsewhere. Here's what you actually receive:
Real URL Example:
https://teams.regardingwork.com/login?sso_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...&user=%7B%22id%22%3A%2229%22%2C%22username%22%3A%22michelini%22%7D
Correct Handler Code:
// CORRECT: Handle SSO callback
function handleSSOCallback() {
const urlParams = new URLSearchParams(window.location.search);
// โ
CORRECT parameter names
const ssoToken = urlParams.get('sso_token'); // NOT 'token'
const userDataStr = urlParams.get('user'); // JSON string
if (ssoToken && userDataStr) {
try {
// Store token with standard key name
localStorage.setItem('hub_jwt_token', ssoToken);
// Parse user data
const userData = JSON.parse(decodeURIComponent(userDataStr));
localStorage.setItem('hub_user_data', JSON.stringify(userData));
// Clean URL
window.history.replaceState({}, document.title, window.location.pathname);
// Redirect to dashboard
window.location.href = '/dashboard';
} catch (error) {
console.error('SSO callback error:', error);
}
}
}
IMPORTANT: Use /api/auth/validate
NOT /api/auth/me
for mini-apps:
// โ
CORRECT: Mini-app token validation
async function validateToken() {
const token = localStorage.getItem('hub_jwt_token');
if (!token) {
redirectToLogin();
return null;
}
try {
const response = await fetch('https://hub.regardingwork.com/api/auth/validate', {
headers: {
'Authorization': `Bearer ${token}` // Must include 'Bearer '
}
});
if (response.ok) {
const data = await response.json();
// โ
CORRECT: Use data.user.username (NOT data.username)
return data.user;
} else {
// Token invalid, redirect to login
redirectToLogin();
return null;
}
} catch (error) {
console.error('Token validation error:', error);
redirectToLogin();
return null;
}
}
function redirectToLogin() {
localStorage.removeItem('hub_jwt_token');
localStorage.removeItem('hub_user_data');
window.location.href = `https://hub.regardingwork.com/login?redirect=${encodeURIComponent(window.location.href)}`;
}
// Simple login check for mini-apps
function checkLoginStatus() {
const token = localStorage.getItem('hub_jwt_token');
const userData = localStorage.getItem('hub_user_data');
if (!token || !userData) {
redirectToLogin();
return false;
}
// Optional: Validate token freshness
// validateToken();
return true;
}
Symptom: Page redirects to Hub so fast you can't see errors
Common Cause: Still trying to validate tokens on your backend instead of using Hub's API
// Add this to your HTML head to debug fast redirects
window.addEventListener('beforeunload', function(e) {
console.log('PAGE REDIRECTING - Current URL:', window.location.href);
console.log('Token in localStorage:', localStorage.getItem('hub_jwt_token'));
});
// Add this to see what's happening
console.log('=== DEBUG INFO ===');
console.log('Current URL:', window.location.href);
console.log('Token exists:', !!localStorage.getItem('hub_jwt_token'));
console.log('Origin header:', window.location.origin);
/api/auth/validate
on YOUR server/api/auth/login
on YOUR server/api/auth/callback
that calls YOUR validationMini-Apps should ONLY call Hub's endpoints directly from frontend!
Symptom: User authenticates successfully at RegardingWork Hub but gets stuck in a login loop
Root Cause: Server redirecting SSO callback to dashboard instead of login component
1. SSO callback processes token โ 2. Server redirects to /dashboard?sso_token=... โ 3. Dashboard has no SSO logic โ redirects to /login โ 4. Login component never sees SSO tokens โ 5. INFINITE LOOP ๐
1. SSO callback processes token โ 2. Server redirects to /login?sso_token=... โ 3. Login component processes SSO tokens โ 4. Login redirects to appropriate dashboard โ 5. PERFECT FLOW ๐
Server SSO callback (CORRECT):
app.get('/auth/callback', async (req, res) => {
const token = req.query.token;
const result = await validateToken(token);
const userData = encodeURIComponent(JSON.stringify(result.user));
// โ
ALWAYS redirect to login component for processing
res.redirect(`/login?sso_token=${internalJWT}&user=${userData}`);
});
Frontend Login component processes SSO:
useEffect(() => {
const ssoToken = urlParams.get('sso_token');
if (ssoToken) {
// Process token, set auth, redirect to dashboard
setAuthToken(ssoToken);
setLocation('/dashboard');
}
}, []);
SSO tokens must be processed by the component that handles authentication redirect logic. If your frontend has a dedicated Login component that handles authentication redirects, your server should redirect SSO callbacks to that component, not directly to the dashboard.
Based on real Teams, Family, and EPD integrations - these are the actual problems you'll encounter!
API calls to /api/auth/validate
return 401
/api/auth/me
instead of /api/auth/validate
Bearer TOKEN
not just TOKEN
token
instead of sso_token
from URL// Check in browser console:
console.log('Token:', localStorage.getItem('hub_jwt_token'));
// Test API call:
fetch('https://hub.regardingwork.com/api/auth/validate', {
headers: { 'Authorization': `Bearer ${localStorage.getItem('hub_jwt_token')}` }
}).then(r => r.json()).then(console.log);
Getting validation response but user data is undefined
// โ WRONG:
const username = response.username;
// โ
CORRECT:
const username = response.user.username;
Open browser DevTools โ Network tab and look for:
sso_token
and user
parametersAuthorization: Bearer ...
header// Verify token format in console:
const token = localStorage.getItem('hub_jwt_token');
console.log('Token parts:', token.split('.').length); // Should be 3
console.log('Token preview:', token.substring(0, 50) + '...');
sso_token
not token
hub_jwt_token
/api/auth/validate
endpointBearer
prefixresponse.user.username
Last updated: September 2025 | Request SSO Access | All Documentation
RegardingWork Hub v1.0