Skip to main content

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

  1. Receives the user's auth headers from Diosc
  2. Validates the token using your auth system
  3. 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 header
  • X-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://..."
}
}
FieldTypeRequiredDescription
userIdstringYesUnique user identifier
displayNamestringNoName shown in UI
emailstringNoUser's email
rolesstring[]NoRole identifiers for role-based behavior
metadataobjectNoAdditional 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

  1. Configure resolver URL in Admin Portal
  2. Connect to assistant with valid user token
  3. Check session shows correct user info
  4. 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