Hey there! Setting up authentication in ToolJet can feel like trying to solve a Rubik’s cube blindfolded, especially when you’re dealing with enterprise-grade security requirements. I’ve been there, and I’m here to walk you through it step by step, making sure you understand each piece of the puzzle. Let’s dive into the world of ToolJet authentication and get your self-hosted app secured like a fortress!
Getting Started with ToolJet Authentication
First things first, let’s understand the basics. ToolJet is a powerful low-code platform that lets you build internal tools quickly. When you’re self-hosting, you have full control over your authentication setup, which is both a blessing and a challenge. Think of it like building your own house – you get to choose the locks, the alarm system, and who gets keys.
Before we jump into the nitty-gritty, make sure you have ToolJet properly installed and running in your self-hosted environment. You’ll need access to your ToolJet server’s configuration files and database. If you haven’t done this yet, grab a coffee and follow the official installation guide – we’ll be waiting!
The Authentication Landscape
ToolJet supports several authentication methods, each with its own strengths:
- OIDC (OpenID Connect): Great for modern apps, works well with providers like Auth0, Okta, and Google
- SAML: The enterprise standard, perfect for large organizations with existing IdPs
- LDAP: Ideal for companies with Active Directory or other directory services
- Role-Based Access Control (RBAC): Essential for managing who can access what
Let’s explore each of these in detail, with practical examples and code snippets where needed.
OIDC Authentication: The Modern Approach
OIDC is like the cool new kid on the block – it’s modern, flexible, and works with almost everything. If you’re integrating with cloud services or want a seamless user experience, OIDC is your friend.
Setting Up OIDC in ToolJet
First, you’ll need to register your application with your OIDC provider. Let’s use Auth0 as an example, but the process is similar for other providers.
Step 1: Create an OIDC Application
Head over to your Auth0 dashboard and create a new application. Give it a name like “ToolJet Enterprise” and select “Regular Web Application” as the type.
// Auth0 Application Configuration
{
"name": "ToolJet Enterprise",
"type": "regular_web_app",
"callbacks": ["https://your-tooljet-domain.com/api/auth/oidc/callback"],
"allowed_logout_urls": ["https://your-tooljet-domain.com"],
"allowed_origins": ["https://your-tooljet-domain.com"]
}
Step 2: Configure ToolJet Environment Variables
Now, let’s configure ToolJet to use this OIDC provider. You’ll need to set several environment variables in your ToolJet deployment.
# Docker Compose Configuration for OIDC
version: '3.8'
services:
tooljet:
image: tooljet/tooljet:latest
environment:
# OIDC Configuration
AUTH_STRATEGY: 'oidc'
OIDC_CLIENT_ID: 'your-auth0-client-id'
OIDC_CLIENT_SECRET: 'your-auth0-client-secret'
OIDC_ISSUER: 'https://your-tenant.auth0.com/'
OIDC_SCOPES: 'openid profile email'
OIDC_CALLBACK_URL: 'https://your-tooljet-domain.com/api/auth/oidc/callback'
# Additional Security Settings
JWT_SECRET: 'your-super-secret-jwt-key'
SESSION_SECRET: 'your-session-secret-key'
ports:
- "3000:3000"
volumes:
- tooljet-data:/var/lib/tooljet
networks:
- tooljet-network
networks:
tooljet-network:
driver: bridge
volumes:
tooljet-data:
Step 3: Configure OIDC Provider Settings
In your Auth0 dashboard, you’ll need to configure the application settings carefully. Here’s what you should set:
{
"allowed_logout_urls": ["https://your-tooljet-domain.com"],
"allowed_origins": ["https://your-tooljet-domain.com"],
"callbacks": ["https://your-tooljet-domain.com/api/auth/oidc/callback"],
"jwt_configuration": {
"lifetime_in_seconds": 3600,
"secret_encoded": true
}
}
Testing Your OIDC Setup
Once you’ve configured everything, it’s time to test. Navigate to your ToolJet instance and try logging in. You should be redirected to your OIDC provider’s login page. After successful authentication, you’ll be redirected back to ToolJet.
// Example OIDC Callback Handler (for reference)
app.get('/api/auth/oidc/callback', async (req, res) => {
try {
const code = req.query.code;
const tokenResponse = await fetch('https://your-tenant.auth0.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: process.env.OIDC_CLIENT_ID,
client_secret: process.env.OIDC_CLIENT_SECRET,
redirect_uri: process.env.OIDC_CALLBACK_URL,
code: code
})
});
const tokens = await tokenResponse.json();
// Process tokens and create session
res.redirect('/dashboard');
} catch (error) {
console.error('OIDC callback error:', error);
res.redirect('/login?error=authentication_failed');
}
});
SAML Authentication: The Enterprise Standard
SAML is like the reliable old friend – it’s been around forever and works with almost every enterprise identity provider. If you’re dealing with large organizations, government agencies, or companies with complex IT infrastructure, SAML is your go-to solution.
SAML Setup in ToolJet
SAML configuration is a bit more involved than OIDC, but don’t worry – I’ll guide you through every step.
Step 1: Choose Your SAML Provider
Popular SAML providers include:
- Okta
- Azure AD (Entra ID)
- OneLogin
- PingIdentity
- ADFS (Microsoft)
Let’s use Okta as our example, but the concepts apply to all providers.
Step 2: Configure Okta Application
In Okta, create a new SAML 2.0 application:
// Okta SAML Application Configuration
{
"label": "ToolJet Enterprise",
"signOnMode": "SAML_2.0",
"appSettings": {
"ssoUrl": "https://your-tooljet-domain.com/api/auth/saml/acs",
"audienceUri": "https://your-tooljet-domain.com",
"responseSigned": true,
"assertionSigned": true
}
}
Step 3: ToolJet SAML Configuration
Now, let’s configure ToolJet to work with your SAML provider:
# ToolJet SAML Configuration in docker-compose.yml
version: '3.8'
services:
tooljet:
image: tooljet/tooljet:latest
environment:
# SAML Configuration
AUTH_STRATEGY: 'saml'
SAML_ISSUER: 'tooljet-enterprise'
SAML_CALLBACK_URL: 'https://your-tooljet-domain.com/api/auth/saml/acs'
SAML_CERT: |
MIIDXTCCAkWgAwIBAgIJAJC1HiIAZAiUMA0GCSqGSIb3Qa661E8=
SAML_IDP_SSO_TARGET_URL: 'https://your-tenant.okta.com/app/tooljet/sso/saml'
SAML_IDP_ENTITY_ID: 'https://your-tenant.okta.com/saml2/service-provider/spomc1example'
# SAML Attributes
SAML_NAME_IDENTIFIER_FORMAT: 'emailAddress'
SAML_ATTRIBUTES: 'email,firstName,lastName'
volumes:
- tooljet-data:/var/lib/tooljet
Step 4: Configure SAML Attributes
This is crucial! You need to map SAML attributes to ToolJet user fields. In your Okta application settings:
{
"attributeStatements": [
{
"name": "email",
"values": ["user.email"]
},
{
"name": "firstName",
"values": ["user.firstName"]
},
{
"name": "lastName",
"values": ["user.lastName"]
},
{
"name": "groups",
"values": ["user.groups"]
}
]
}
Advanced SAML Features
SAML offers some powerful features for enterprise security:
NameID Format Configuration
<!-- SAML NameID Format -->
<ns2:NameIDPolicy
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
AllowCreate="true" />
Assertion Consumer Service (ACS) Configuration
// ToolJet SAML ACS Handler
const saml = new samlLib.SAML({
issuer: 'tooljet-enterprise',
callbackUrl: process.env.SAML_CALLBACK_URL,
cert: process.env.SAML_CERT,
entryPoint: process.env.SAML_IDP_SSO_TARGET_URL,
identifierFormat: 'emailAddress'
});
app.post('/api/auth/saml/acs', (req, res) => {
saml.validatePostRequest(req.body.SAMLResponse, (err, profile) => {
if (err) {
return res.status(400).send('Invalid SAML response');
}
// Create or update user in ToolJet
const user = await User.findOneOrCreate({
email: profile.email,
firstName: profile.firstName,
lastName: profile.lastName
});
// Create session
req.login(user, (err) => {
if (err) return res.status(500).send('Login failed');
res.redirect('/dashboard');
});
});
});
LDAP Authentication: Directory Services Integration
LDAP is the backbone of many enterprise identity systems. If your organization uses Active Directory or OpenLDAP, LDAP authentication will feel like coming home. It’s robust, well-documented, and integrates seamlessly with existing infrastructure.
LDAP Setup in ToolJet
Step 1: Configure LDAP Server Connection
Let’s assume you’re using Active Directory. Here’s how to configure ToolJet:
# ToolJet LDAP Configuration
version: '3.8'
services:
tooljet:
image: tooljet/tooljet:latest
environment:
# LDAP Configuration
AUTH_STRATEGY: 'ldap'
LDAP_URL: 'ldap://your-ad-server.company.com:389'
LDAP_BIND_DN: 'CN=ToolJet Service Account,OU=Service Accounts,DC=company,DC=com'
LDAP_BIND_PASSWORD: 'your-strong-password-here'
LDAP_SEARCH_BASE: 'OU=Users,DC=company,DC=com'
LDAP_SEARCH_FILTER: '(sAMAccountName=%{username})'
# LDAP Settings
LDAP_USER_ATTRS: 'mail,givenName,sn,memberOf'
LDAP_GROUP_ATTR: 'memberOf'
LDAP_GROUP_SEARCH_BASE: 'OU=Groups,DC=company,DC=com'
LDAP_GROUP_SEARCH_FILTER: '(member=%{userDn})'
Step 2: Active Directory Service Account Setup
You’ll need to create a service account in your Active Directory:
# PowerShell Script to Create AD Service Account
$ServiceAccount = "CN=ToolJet Service Account,OU=Service Accounts,DC=company,DC=com"
# Create the account
New-ADUser -Name "ToolJet Service Account" `
-SamAccountName "tooljet-svc" `
-UserPrincipalName "tooljet-svc@company.com" `
-AccountPassword (ConvertTo-SecureString "YourStrongPassword123!" -AsPlainText -Force) `
-Enabled $true `
-Path "OU=Service Accounts,DC=company,DC=com"
# Grant read permissions to users and groups
Add-ADPermission -Identity "DC=company,DC=com" `
-User "company\tooljet-svc" `
-ExtendedRight "Read" `
-InheritanceType "All"
Step 3: LDAP Authentication Code Example
// LDAP Authentication Handler
const ldap = require('ldapjs');
async function authenticateLDAP(username, password) {
const client = ldap.createClient({
url: process.env.LDAP_URL
});
try {
// Bind with service account
await client.bind(process.env.LDAP_BIND_DN, process.env.LDAP_BIND_PASSWORD);
// Search for user
const user = await new Promise((resolve, reject) => {
client.search(
process.env.LDAP_SEARCH_BASE,
{
filter: process.env.LDAP_SEARCH_FILTER.replace('%{username}', username),
scope: 'sub'
},
(err, search) => {
let results = [];
search.on('entry', (entry) => {
results.push(entry.object);
});
search.on('error', reject);
search.on('end', () => resolve(results));
}
);
});
if (user.length === 0) {
throw new Error('User not found');
}
// Verify password
const userDn = user[0].dn;
await client.bind(userDn, password);
// Get user details
const userAttrs = user[0];
return {
email: userAttrs.mail[0],
firstName: userAttrs.givenName[0],
lastName: userAttrs.sn[0],
dn: userDn,
groups: userAttrs.memberOf || []
};
} finally {
client.unbind();
}
}
Role-Based Access Control (RBAC): The Security Backbone
Now that we’ve covered authentication methods, let’s talk about authorization. Authentication is about who you are; authorization is about what you can do. RBAC is your best friend here, providing granular control over user permissions.
Setting Up RBAC in ToolJet
Step 1: Define Roles and Permissions
Let’s create a comprehensive role structure:
// Role Definition Schema
const roles = {
admin: {
name: 'Administrator',
permissions: [
'app:create',
'app:read',
'app:update',
'app:delete',
'user:create',
'user:read',
'user:update',
'user:delete',
'settings:manage',
'audit:read',
'api:manage'
]
},
manager: {
name: 'Manager',
permissions: [
'app:read',
'app:update',
'dashboard:manage',
'team:manage',
'reports:read',
'reports:export'
]
},
developer: {
name: 'Developer',
permissions: [
'app:read',
'app:update',
'dashboard:create',
'dashboard:update',
'integration:test'
]
},
viewer: {
name: 'Viewer',
permissions: [
'app:read',
'dashboard:read',
'reports:read'
]
}
};
Step 2: Assign Roles to Users
-- Database Schema for Role Assignment
CREATE TABLE user_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
role_name VARCHAR(50) NOT NULL,
assigned_by UUID REFERENCES users(id),
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
UNIQUE(user_id, role_name)
);
-- Insert role assignments
INSERT INTO user_roles (user_id, role_name, assigned_by)
VALUES
('user-uuid-1', 'admin', 'admin-user-uuid'),
('user-uuid-2', 'manager', 'admin-user-uuid'),
('user-uuid-3', 'developer', 'admin-user-uuid');
Step 3: Permission Checking Middleware
// Express Middleware for Permission Checking
const checkPermission = (requiredPermission) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const userRole = req.user.role;
const rolePermissions = roles[userRole]?.permissions || [];
if (!rolePermissions.includes(requiredPermission)) {
return res.status(403).json({
error: 'Permission denied',
required: requiredPermission,
userRole: userRole
});
}
next();
};
};
// Usage in routes
app.get('/api/apps', checkPermission('app:read'), (req, res) => {
// Only users with app:read permission can access
const apps = await App.findAll();
res.json(apps);
});
app.post('/api/apps', checkPermission('app:create'), async (req, res) => {
// Only users with app:create permission can create apps
const newApp = await App.create(req.body);
res.status(201).json(newApp);
});
Advanced RBAC Features
Dynamic Role Assignment
”`javascript // Dynamic Role Assignment Based on Context async function assignDynamicRoles(user, context) { const dynamicRoles
