User Resolution
The User Resolver is a webhook that tells Diosc who the current user is. When a user connects to an assistant, Diosc calls your resolver to get user identity, roles, and metadata.
Why User Resolution?
Diosc follows BYOA (Bring Your Own Authentication). It doesn't validate tokens itself - it needs you to tell it who the user is. The user resolver bridges your auth system with Diosc.
What the Resolver Does
- Receives the user's auth headers from Diosc
- Validates the token using your auth system
- Returns user identity, roles, and optional metadata
Quick Start
1. Create the Endpoint
// Express example
app.post('/api/diosc/resolve-user', async (req, res) => {
try {
// Get the forwarded auth header
const authHeader = req.headers['x-user-auth-authorization'];
if (!authHeader) {
return res.status(401).json({ error: 'No authorization header' });
}
// Validate with your auth system
const user = await validateToken(authHeader);
if (!user) {
return res.status(401).json({ error: 'Invalid token' });
}
// Return user info
res.json({
userId: user.id,
displayName: user.name,
email: user.email,
roles: user.roles,
metadata: {
department: user.department,
tenantId: user.tenantId
}
});
} catch (error) {
res.status(500).json({ error: 'Internal error' });
}
});
2. Configure in Diosc
In Admin Portal → Assistant Settings:
User Resolver URL: https://your-api.com/api/diosc/resolve-user
Timeout: 5000ms
Or via API:
curl -X PATCH https://diosc-hub.example.com/admin/assistants/{id} \
-H "Authorization: Bearer admin-token" \
-H "Content-Type: application/json" \
-d '{
"userResolverUrl": "https://your-api.com/api/diosc/resolve-user",
"userResolverTimeoutMs": 5000
}'
3. Test It
# Simulate Diosc calling your resolver
curl -X POST https://your-api.com/api/diosc/resolve-user \
-H "X-User-Auth-Authorization: Bearer <user-token>" \
-H "Content-Type: application/json"
Request Format
Diosc sends a POST request with the user's auth headers prefixed with X-User-Auth-:
POST /api/diosc/resolve-user HTTP/1.1
Host: your-api.com
Content-Type: application/json
X-User-Auth-Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
X-User-Auth-Cookie: session=abc123
X-Request-Id: req_xyz789
Headers forwarded:
X-User-Auth-Authorization- User's Authorization headerX-User-Auth-Cookie- User's cookies (if any)X-User-Auth-*- Any other auth-related headers
Response Format
Success Response (200)
{
"userId": "user_123",
"displayName": "John Doe",
"email": "john@example.com",
"roles": ["admin", "sales"],
"metadata": {
"tenantId": "acme-corp",
"department": "Sales",
"region": "APAC",
"avatarUrl": "https://..."
}
}
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Unique user identifier |
displayName | string | No | Name shown in UI |
email | string | No | User's email |
roles | string[] | No | Role identifiers for role-based behavior |
metadata | object | No | Additional context (tenant, department, etc.) |
Error Response (401/403)
{
"error": "Token expired",
"code": "TOKEN_EXPIRED"
}
When the resolver returns an error:
- 401: User not authenticated → Diosc prompts for login
- 403: User not authorized → Diosc shows access denied
- 500+: System error → Diosc uses default role
Common Implementations
JWT Token Validation
import jwt from 'jsonwebtoken';
app.post('/api/diosc/resolve-user', (req, res) => {
const authHeader = req.headers['x-user-auth-authorization'];
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = authHeader.slice(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
res.json({
userId: decoded.sub,
displayName: decoded.name,
email: decoded.email,
roles: decoded.roles || ['default'],
metadata: {
tenantId: decoded.tenant_id
}
});
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
}
return res.status(401).json({ error: 'Invalid token' });
}
});
Session-Based Auth
app.post('/api/diosc/resolve-user', async (req, res) => {
const cookies = req.headers['x-user-auth-cookie'];
if (!cookies) {
return res.status(401).json({ error: 'No session' });
}
// Parse session ID from cookies
const sessionId = parseCookies(cookies).sessionId;
// Look up session in your database
const session = await sessionStore.get(sessionId);
if (!session || session.expiresAt < Date.now()) {
return res.status(401).json({ error: 'Session expired' });
}
// Get user from session
const user = await userStore.get(session.userId);
res.json({
userId: user.id,
displayName: user.name,
email: user.email,
roles: user.roles
});
});
OAuth/OIDC
import { OAuth2Client } from 'google-auth-library';
const oauthClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
app.post('/api/diosc/resolve-user', async (req, res) => {
const authHeader = req.headers['x-user-auth-authorization'];
const token = authHeader?.replace('Bearer ', '');
try {
const ticket = await oauthClient.verifyIdToken({
idToken: token,
audience: process.env.GOOGLE_CLIENT_ID
});
const payload = ticket.getPayload();
// Look up user in your database
const user = await userStore.findByEmail(payload.email);
res.json({
userId: user.id,
displayName: payload.name,
email: payload.email,
roles: user.roles,
metadata: {
picture: payload.picture
}
});
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
Multi-Tenancy
Include tenant information in the response:
res.json({
userId: user.id,
displayName: user.name,
roles: user.roles,
metadata: {
tenantId: user.tenantId, // Used for data isolation
tenantName: user.tenantName // For display
}
});
Diosc uses tenantId for:
- Session isolation
- Conversation storage
- MCP request headers (X-Tenant-Id)
Role Mapping
Map your roles to Diosc role configurations:
// Your roles → Diosc roles
function mapRoles(userPermissions: string[]): string[] {
const roleMap = {
'SUPER_ADMIN': 'admin',
'MANAGER': 'admin',
'STAFF': 'editor',
'CUSTOMER': 'viewer',
'GUEST': 'viewer'
};
return userPermissions
.map(p => roleMap[p])
.filter(Boolean);
}
res.json({
userId: user.id,
roles: mapRoles(user.permissions)
});
Error Handling
Handle errors gracefully:
app.post('/api/diosc/resolve-user', async (req, res) => {
try {
// ... validation logic
} catch (error) {
console.error('User resolver error:', error);
// Don't expose internal errors
if (error.code === 'TOKEN_EXPIRED') {
return res.status(401).json({
error: 'Your session has expired. Please log in again.',
code: 'TOKEN_EXPIRED'
});
}
if (error.code === 'INVALID_TOKEN') {
return res.status(401).json({
error: 'Invalid authentication.',
code: 'INVALID_TOKEN'
});
}
// Generic error (don't expose details)
res.status(500).json({
error: 'Unable to verify identity. Please try again.'
});
}
});
Testing
Unit Test
describe('User Resolver', () => {
it('returns user for valid token', async () => {
const response = await request(app)
.post('/api/diosc/resolve-user')
.set('X-User-Auth-Authorization', 'Bearer valid-token');
expect(response.status).toBe(200);
expect(response.body.userId).toBeDefined();
expect(response.body.roles).toBeInstanceOf(Array);
});
it('returns 401 for expired token', async () => {
const response = await request(app)
.post('/api/diosc/resolve-user')
.set('X-User-Auth-Authorization', 'Bearer expired-token');
expect(response.status).toBe(401);
expect(response.body.code).toBe('TOKEN_EXPIRED');
});
it('returns 401 for missing token', async () => {
const response = await request(app)
.post('/api/diosc/resolve-user');
expect(response.status).toBe(401);
});
});
Integration Test with Diosc
- Configure resolver URL in Admin Portal
- Connect to assistant with valid user token
- Check session shows correct user info
- Verify roles are applied correctly
Troubleshooting
Resolver Not Called
- Check URL is reachable from Diosc Hub
- Verify HTTPS certificate is valid
- Check firewall/network settings
Timeout Errors
- Increase
userResolverTimeoutMs(default 5000ms) - Optimize token validation speed
- Consider caching validated tokens
Wrong Roles Applied
- Check role names match Diosc role configs
- Verify roles array is returned (not object)
- Check role priority settings
Next Steps
- Roles - Configure role-based behavior
- Admin Portal - Configure resolver in UI
- BYOA - Understand the auth model