A practical guide to how to set up a whitelist on your qbcore fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.
Overview
This guide explains how to implement a robust whitelist for a QBCore FiveM server. It covers architecture, secure server-side validation, database choices, a practical server-side implementation example, admin tooling, testing, and deployment/maintenance best practices. The patterns shown apply to QBCore and are easy to adapt to ESX or to frameworks using ox_lib. For related management tooling and monitoring you can explore Stellar AI's tools at https://trystellarai.com/app.
Architecture and Data Model
Keep the whitelist logic entirely on the server. The client cannot be trusted for any access control decision: do all validation inside the player connection flow and any command handlers. Typical components:
- Connection gate (playerConnecting deferrals) — checks identifiers before allowing join.
- Persistent store (MySQL/oxmysql) — canonical whitelist entries, metadata (who added, reason, expiration).
- Admin controls — commands and optional UI for adding/removing entries.
- Audit and logging — record join attempts and admin actions to detect abuse.
Minimal data model (SQL):
CREATE TABLE whitelist (
id INT AUTO_INCREMENT PRIMARY KEY,
identifier VARCHAR(64) NOT NULL, -- e.g. steam:110000...
added_by VARCHAR(64),
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
reason TEXT NULL,
INDEX (identifier)
);
Implementation Choices
Storage
Use a server-side database rather than a local file for production. MySQL-backed stores (mysql-async or oxmysql) scale and persist across restarts. JSON or flat-file storage can be useful for testing or small communities but is fragile for production.
Identifiers to use
Whitelist by stable server-side identifiers: Steam ID ("steam:..."), Rockstar License ("license:..."), xbl or discord (when available). Do not rely on player-supplied names or client-side variables. Always parse and canonicalize identifiers on the server.
Server-Side Validation and Security
Enforce these rules:
- Never trust client input. Do not allow the client to assert which identifier it is — retrieve identifiers server-side via GetPlayerIdentifiers in the playerConnecting event or via QBCore hooks.
- Use deferrals. Use FiveM's deferrals to pause connection, run checks, and return clear messages to the client.
- Rate-limit and log. Track repeated failed connection attempts and log them; consider temporary bans for obvious abuse vectors.
- Sanitize database inputs. Use parameterized queries (MySQL.Async or your driver) rather than string concatenation.
- Principle of least privilege. Grant the minimum database permissions required for the whitelist table (SELECT/INSERT/UPDATE/DELETE).
Practical Server Script Example (QBCore)
The example below demonstrates a server-side whitelist that uses FiveM deferrals and MySQL.Async. Adapt the DB calls to oxmysql or your preferred driver. The logic: collect identifiers, check DB, allow or deny via deferrals.
-- server.lua (example)
local QBCore = exports['qb-core']:GetCoreObject()
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
local src = source
deferrals.defer()
deferrals.update("Checking whitelist...")
-- Collect identifiers server-side
local identifiers = {}
for _, id in ipairs(GetPlayerIdentifiers(src)) do
identifiers[id:match('^(%w+:%w+)')] = id
end
-- Prefer steam or license, fallback to first identifier
local identifier
if identifiers['steam'] then identifier = identifiers['steam']
elseif identifiers['license'] then identifier = identifiers['license']
else
for _,v in pairs(identifiers) do identifier = v; break end
end
if not identifier then
deferrals.done("Could not resolve your identifiers. Ensure your client is running Steam/launcher.")
return
end
-- Secure DB lookup (MySQL.Async shown; replace with oxmysql if used)
MySQL.Async.fetchAll('SELECT * FROM whitelist WHERE identifier = @id', {
['@id'] = identifier
}, function(result)
if result and #result > 0 then
deferrals.done() -- allow
else
deferrals.done("You are not whitelisted on this server.")
end
end)
end)
-- Admin commands (server-side only)
RegisterCommand('wladd', function(source, args, raw)
if source ~= 0 and not IsPlayerAceAllowed(source, 'admin') then
TriggerClientEvent('chat:addMessage', source, { args = { '^1SYSTEM', 'No permission' } })
return
end
local identifier = args[1]
if not identifier then
print('Usage: /wladd ')
return
end
MySQL.Async.execute('INSERT INTO whitelist (identifier, added_by) VALUES (@id, @added)', {
['@id'] = identifier, ['@added'] = (source == 0 and 'console' or tostring(GetPlayerName(source)))
}, function(rows)
print('Whitelist entry added:', identifier)
end)
end, true)
Notes:
- Adapt the admin permission check to your server's ACLs. IsPlayerAceAllowed is a common FiveM ACE check.
- For QBCore admin menus, expose server commands and keep UI interactions purely as clients sending admin requests to the server; validate the caller server-side before applying changes.
Admin Tools and UI
Consider two admin interfaces:
- Command-line commands for quick adds/removals (as shown above).
- Context menus or web panels that call server endpoints. If you use ox_lib, register client context menus but perform all whitelist changes through server events that verify the admin's identity and permissions.
If you build a web dashboard, expose a server-side API (not client) that only trusted backend components call. Protect that API with keys and IP restrictions.
Testing and QA
Before production rollout:
- Test fresh connections with non-whitelisted identifiers and confirm deferral messages are clear.
- Test admin flows: add, remove, expire entries and verify changes apply immediately.
- Test edge cases: multiple identifiers, identifier collisions, partial information (no steam), and DB failures.
- Simulate DB downtime and ensure your server responds with a clear message (prefer safe denial rather than allowing everyone when DB unavailable).
For logging and quick diagnosis, log join attempts with timestamp, identifier, result, and admin actions. Keep logs in a separate retention storage for auditing.
Deployment and Maintenance
Deployment checklist:
| Task | Action | Why |
|---|---|---|
| Staging test | Deploy to staging server and run integration test suite | Catch regressions before production |
| DB Backup | Schedule nightly backups of whitelist table | Recover from accidental deletes or corruption |
| Monitoring | Monitor DB connectivity, deferral failure rates, and repeated rejects | Detect outages or attack patterns early |
| Access control | Review ACEs and admin accounts monthly | Limit privilege creep |
For operational tooling and analytics integration, you can connect server logs to external systems; to inspect and manage lists interactively consider secure apps such as https://trystellarai.com/app.
Maintenance Best Practices
Maintain whitelist hygiene:
- Expire entries for temporary whitelists and implement automatic cleanup jobs.
- Keep an audit trail (who added/removed and when).
- Rotate database credentials periodically and restrict DB user permissions.
- Document admin procedures: who can add, remove, and how appeals are handled.
For deeper procedural guidance and updates, review your team’s operations notes or an operations blog post: https://trystellarai.com/blog.
Roblox Considerations (Server Authority and RemoteEvents)
If you also maintain Roblox servers or scripts, follow these parallels:
- Keep decisions server-authoritative. Clients may request actions via RemoteEvents, but the server must validate every request before changing whitelist state.
- Use DataStore for persistence and implement robust failure handling: DataStore calls may fail, so implement retries and degrade gracefully (e.g., deny joins if you cannot confirm whitelist status reliably).
- Log and audit RemoteEvent calls and DataStore errors to diagnose consistency issues.
Practical Checklist
- Choose a storage backend (MySQL/oxmysql preferred for production).
- Implement playerConnecting deferrals that look up identifiers server-side.
- Do not use client-provided identifiers; read them with GetPlayerIdentifiers.
- Provide secure admin commands with ACE checks or QBCore authority checks.
- Log and monitor join attempts and admin changes.
- Back up the whitelist table and test restore procedures.
- Test DB failure scenarios; choose deny-by-default behavior if DB unavailable.