User Authentication Integration
User Authentication Integration
VJCAD as a secondary development platform does not include a built-in user system. Through the user info passing + backend auth callback mechanism, developers can seamlessly integrate their business system's user identity and permission control into VJCAD's collaborative editing workflow.
Online Examples
| Example | Description | Link |
|---|---|---|
| User Auth Callback | Demonstrates business system login, user identity passing, and permission check callback | Online Demo{target="_blank"} |
Overall Architecture
How It Works
- Frontend passes user info: Developers pass business system user info (userId, userName, sessionId) via the
userInfoconfig when creatingMainView - Requests auto-carry: All VJCAD operation requests automatically carry this user info in the request body
- Backend auth callback: VJCAD backend (odasvr) sends an HTTP request to the configured auth callback URL before executing each operation, sending user info and operation info to the business system
- Business system decision: Business system returns allow or deny based on its own permission logic
- Result feedback: If denied, the frontend triggers the
onAuthErrorcallback, and developers can handle it (e.g., redirect to login, show no-permission message)
Frontend Integration
Basic Usage
const { MainView, initCadContainer } = vjcad;
const cadView = new MainView({
serviceUrl: "http://127.0.0.1:27660/api/v1",
accessToken: "your-access-token",
// Pass business system user info
userInfo: {
userId: "user_12345", // Required, unique user identifier
userName: "John Doe", // Optional, display name (shows userId when empty)
sessionId: "sess_abc123", // Optional, business system session ID
},
// Auth error callback
onAuthError: (error) => {
// error.errorCode: "session_expired" | "no_permission" | custom error code
// error.message: Human-readable error message
// error.operation: Operation name that triggered the error
if (error.errorCode === 'session_expired') {
window.location.href = '/login'; // Redirect to login
} else {
alert('Insufficient permission: ' + error.message);
}
},
});
initCadContainer("cad-app", cadView);Complete Integration Example (with Login Flow)
const { MainView, initCadContainer } = vjcad;
// ========== Step 1: Call business system login API ==========
async function login(userId, password) {
const resp = await fetch('http://your-auth-server/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, password })
});
return await resp.json();
// Return format: { success: true, sessionId: "sess_xxx", userName: "John Doe" }
}
// ========== Step 2: Create MainView after successful login ==========
const loginResult = await login('test_user', '123456');
if (loginResult.success) {
const cadView = new MainView({
serviceUrl: "http://127.0.0.1:27660/api/v1",
accessToken: "your-access-token",
userInfo: {
userId: loginResult.userId,
userName: loginResult.userName,
sessionId: loginResult.sessionId,
},
onAuthError: (error) => {
if (error.errorCode === 'session_expired') {
alert('Session expired, please login again');
} else {
alert(error.message);
}
},
});
initCadContainer("cad-app", cadView);
}Update User Info at Runtime
When session is renewed or user switches, you can dynamically update via the updateUserInfo method:
// Update sessionId after session renewal
cadView.updateUserInfo({
userId: "user_12345",
userName: "John Doe",
sessionId: "new_sess_def456", // New sessionId
});Behavior Without userInfo
If userInfo is not configured, VJCAD behaves exactly as before:
- Uses browser fingerprint as default author identifier
- Does not trigger backend auth callback
- Maintains backward compatibility
Backend Configuration
config.json Configuration
Configure auth callback in the vjmap service backend's config.json:
{
"map": {
"auth_callback": {
"url": "http://127.0.0.1:3200/api/auth/check",
"method": "POST",
"timeout": 5000,
"fail_policy": "deny"
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | empty | Auth callback API URL. Empty or unset skips auth callback |
method | string | "POST" | HTTP method, supports POST and GET |
timeout | number | 5000 | Timeout in milliseconds |
fail_policy | string | "deny" | Policy when callback fails (timeout/network error): "deny" reject operation, "allow" allow |
Note
If auth_callback is not configured, VJCAD backend will not perform any auth callback; all operations are only controlled by SDK's own secretKey/accessKey.
Callback Trigger Timing
Every VJCAD operation triggers the auth callback (no caching). The business system can perform in the callback:
- Permission verification
- Operation logging
- Quota statistics
- Workflow triggering
- Other custom logic
Operations Covered by Callback
| Operation | operation value | Description |
|---|---|---|
| List drawings | listVJCADDraws | Get drawing list |
| Get drawing data | getVJCADData | Open/read drawing |
| Save changes | saveVJCADPatch | Save edit content |
| Delete drawing | deleteVJCADDraw | Delete entire drawing or specified version |
| Create branch | createVJCADBranch | Create new branch |
| Delete branch | deleteVJCADBranch | Delete branch |
| Merge branch | mergeVJCADBranch | Merge branch |
Auth Callback API Specification
Request Format (POST)
{
"userId": "user_12345",
"sessionId": "sess_abc123",
"userName": "John Doe",
"operation": "saveVJCADPatch",
"resource": {
"type": "imports",
"mapId": "drawing-001",
"version": "v1",
"designPath": "",
"branch": "main"
}
}| Field | Description |
|---|---|
userId | Unique user identifier |
sessionId | Business system session ID |
userName | User display name |
operation | Operation name (see table above) |
resource.type | Drawing type: "imports" (imported drawing) or "designs" (design drawing) |
resource.mapId | Map ID (imports type) |
resource.version | Version number |
resource.designPath | Design path (designs type) |
resource.branch | Branch name |
Response Format
{
"allowed": true,
"reason": "no_permission",
"message": "Only administrators can delete drawings"
}| Field | Type | Description |
|---|---|---|
allowed | boolean | true allow operation, false deny operation |
reason | string | Denial reason code (frontend receives via errorCode). Suggested values: "session_expired", "no_permission", custom error codes |
message | string | Human-readable error message (frontend receives via error.message) |
Backend Auth Service Example (Node.js)
Below is a complete Node.js auth service example with login, logout, and permission check APIs:
/**
* VJCAD Auth Callback Service Example (Node.js + Express)
*
* Install: npm install express
* Start: node auth-server.js
*
* Then configure in odasvr's config.json:
* {
* "map": {
* "auth_callback": {
* "url": "http://127.0.0.1:3200/api/auth/check",
* "method": "POST",
* "timeout": 5000,
* "fail_policy": "deny"
* }
* }
* }
*/
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = 3200;
app.use(express.json());
// CORS support (frontend may be on different port)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
if (req.method === 'OPTIONS') return res.sendStatus(200);
next();
});
// ==================== User data (connect to database in real project) ====================
const users = {
'admin': { password: '123456', userName: 'Admin', role: 'admin' },
'editor_user': { password: '123456', userName: 'Editor', role: 'editor' },
'readonly_user': { password: '123456', userName: 'Viewer', role: 'viewer' },
};
// ==================== Session management ====================
const sessions = new Map();
const SESSION_TTL = 30 * 60 * 1000; // 30 minutes
// POST /api/login - Login
app.post('/api/login', (req, res) => {
const { userId, password } = req.body;
const user = users[userId];
if (!user || user.password !== password) {
return res.json({ success: false, message: 'Invalid username or password' });
}
const sessionId = 'sess_' + crypto.randomBytes(16).toString('hex');
sessions.set(sessionId, {
userId, userName: user.userName, role: user.role,
expiresAt: Date.now() + SESSION_TTL
});
res.json({ success: true, userId, userName: user.userName, sessionId, role: user.role });
});
// POST /api/logout - Logout
app.post('/api/logout', (req, res) => {
sessions.delete(req.body.sessionId);
res.json({ success: true });
});
// ==================== Permission check API (called by VJCAD backend) ====================
app.post('/api/auth/check', (req, res) => {
const { userId, sessionId, operation, resource } = req.body;
// 1. Verify session
const session = sessions.get(sessionId);
if (sessionId && (!session || Date.now() > session.expiresAt)) {
if (session) sessions.delete(sessionId);
return res.json({
allowed: false,
reason: 'session_expired',
message: 'Session expired, please login again'
});
}
// 2. Determine permission by role
const role = session?.role || 'guest';
// Blocked user
if (role === 'blocked') {
return res.json({
allowed: false, reason: 'no_permission', message: 'This user has been blocked'
});
}
// Read-only user cannot perform write operations
if (role === 'viewer' && !['listVJCADDraws', 'getVJCADData'].includes(operation)) {
return res.json({
allowed: false, reason: 'no_permission', message: 'Read-only users cannot perform write operations'
});
}
// Non-admin cannot delete
if (['deleteVJCADDraw', 'deleteVJCADBranch'].includes(operation) && role !== 'admin') {
return res.json({
allowed: false, reason: 'no_permission', message: 'Only administrators can perform delete operations'
});
}
// 3. Pass — can also do logging, quota stats, etc.
console.log(`[AUTH] ${userId} ${operation} -> ALLOW`);
res.json({ allowed: true });
});
// Periodic cleanup of expired sessions
setInterval(() => {
const now = Date.now();
for (const [id, s] of sessions) {
if (now > s.expiresAt) sessions.delete(id);
}
}, 60000);
app.listen(PORT, () => {
console.log(`Auth service running at http://127.0.0.1:${PORT}`);
});Permission Check Flow
VJCAD backend permission check is two-layer serial:
- SDK permission (secretKey/accessKey) — Map-level access control
- Business auth callback (auth_callback) — User-level operation permission
Backward Compatibility
- No
auth_callbackconfigured → No business auth callback - No
userInfopassed → No business auth callback, uses browser fingerprint as author - Both cases are fully compatible with existing behavior
User Identity in Collaborative Editing
After configuring userInfo, the following places in collaborative editing use the real username (instead of machine fingerprint):
- Patch author field:
authorfield usesuserName(oruserIdwhen empty) - Create branch author field
- Merge branch author field
- Author source in conflict info
FAQ
Q: Can VJCAD run normally without starting the auth service?
Yes. If auth_callback is not configured in config.json, or auth_callback.url is empty, all operations execute normally without auth callback.
Q: What happens if the auth service goes down?
Depends on fail_policy configuration:
"deny"(default): All operations with userId will be rejected, returning "service unavailable""allow": All operations are allowed when auth service is unavailable
Q: Does callback on every operation affect performance?
Auth callback is synchronous and adds latency to each operation (typically 1-10ms in intranet). Recommendations:
- Deploy auth service in same intranet as VJCAD backend
- Set reasonable
timeout(suggest 3000-5000ms) - Keep auth service response as fast as possible
Q: Can we only do auth callback for write operations?
Auth callback triggers for all 7 operation types. Business system can return { "allowed": true } directly for read operations in the callback to quickly allow.