OnUserApproved
OnUserApproved is called by Oxide when a client is trying to connect to the server, after the connection has been read but before the player is spawned. Returning a string rejects the connection with that message; returning null lets it through.
object OnUserApproved(string username, string userid, string ipaddr)Parameters
| Parameter | Type | Description |
|---|---|---|
username | string | The display name the connecting client sent to the server. Not yet sanitized or validated against a stored Steam profile, so treat it as untrusted input. |
userid | string | The Steam ID of the connecting client as a string, e.g. "76561198000000000". Use it to look up bans, whitelists or stored player data. |
ipaddr | string | The IP address the connection originates from, as a string. Useful for IP-based bans, proxy checks or regional filtering. |
Return value
Return null to allow the connection to continue normally. Return a string to reject the connection; the string is shown to the client as the reason. Returning anything that is not a string (including a bool) is treated as no return, because the handler only understands string rejections here.
How it works
The hook fires from Oxide's IOnUserApprove connection handler. At that point the server already knows the Steam ID, display name and IP of the incoming client, but the connection has not been turned into a BasePlayer yet, so you cannot use player lists or most player APIs. Typical triggers are a fresh client joining, a reconnecting player and a client resuming after a timeout.
All three parameters are plain strings, so there is no overload narrowing to do: every call passes the same types. userid is the key for ban and whitelist lookups; ipaddr lets you block ranges or shared connections; username is mostly informational and can be spoofed, so never authorise on it alone.
Returning a non-null string makes the connection handler call ConnectionAuth.Reject with your text, and the client sees it as the disconnect reason. Returning null keeps the default path and the client continues into the normal approval and spawn flow. Because the hook runs on the connection thread path, keep the body fast: no file writing, no waiting on other plugins in loops.
Example plugin: IP ban list enforced at connect time
using UnityEngine;
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("IpConnectionGuard", "RustPluginGenerator", "1.0.0")]
[Description("Rejects incoming Rust connections from banned IP addresses and CIDR ranges.")]
public class IpConnectionGuard : RustPlugin
{
private const string PermManage = "ipconnectionguard.manage";
private class PluginConfig
{
[JsonProperty("Banned IPs and ranges (e.g. 1.2.3.4 or 10.0.0.0/8)")]
public List<string> Banned = new List<string>();
[JsonProperty("Rejection message")]
public string RejectionMessage = "Your IP address is not allowed on this server.";
}
private class StoredData
{
public List<string> Banned = new List<string>();
}
private PluginConfig config;
private StoredData data;
private readonly Dictionary<uint, uint> prefixes = new Dictionary<uint, uint>();
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Banned"] = "Your IP address is not allowed on this server.",
["Added"] = "Added {0} to the IP guard list.",
["Removed"] = "Removed {0} from the IP guard list.",
["List"] = "{0} entries are blocked.",
["NoPerm"] = "You do not have permission to use this command."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermManage, this);
LoadData();
BuildPrefixes();
}
private void LoadData()
{
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
if (data.Banned.Count == 0 && config.Banned != null)
{
data.Banned.AddRange(config.Banned);
}
}
private void BuildPrefixes()
{
prefixes.Clear();
foreach (string entry in data.Banned)
{
string[] parts = entry.Split('/');
if (parts.Length != 2) continue;
uint ip, maskBits;
if (!TryParseIp(parts[0], out ip) || !uint.TryParse(parts[1], out maskBits) || maskBits > 32) continue;
uint mask = maskBits == 0 ? 0u : uint.MaxValue << (int)(32 - maskBits);
prefixes[ip & mask] = maskBits;
}
}
private static bool TryParseIp(string text, out uint value)
{
value = 0;
string[] octets = text.Split('.');
if (octets.Length != 4) return false;
for (int i = 0; i < 4; i++)
{
int octet;
if (!int.TryParse(octets[i], out octet) || octet < 0 || octet > 255) return false;
value = (value << 8) | (uint)octet;
}
return true;
}
private bool IsBlocked(string ipaddr)
{
foreach (string entry in data.Banned)
{
if (entry.IndexOf('/') < 0 && entry == ipaddr) return true;
}
uint ip;
if (!TryParseIp(ipaddr, out ip)) return false;
foreach (KeyValuePair<uint, uint> pair in prefixes)
{
uint mask = pair.Value == 0 ? 0u : uint.MaxValue << (int)(32 - pair.Value);
if ((ip & mask) == pair.Key) return true;
}
return false;
}
private object OnUserApproved(string username, string userid, string ipaddr)
{
if (string.IsNullOrEmpty(ipaddr)) return null;
if (!IsBlocked(ipaddr)) return null;
return Msg("Banned");
}
[ConsoleCommand("ipguard.add")]
private void CmdAdd(ConsoleSystem.Arg arg)
{
if (!arg.IsAdmin && (arg.Player() == null || !permission.UserHasPermission(arg.Player().UserIDString, PermManage)))
{
arg.ReplyWith(Msg("NoPerm"));
return;
}
string entry = arg.GetString(0, "");
if (string.IsNullOrEmpty(entry)) return;
if (!data.Banned.Contains(entry)) data.Banned.Add(entry);
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
BuildPrefixes();
arg.ReplyWith(Msg("Added", null, entry));
}
[ConsoleCommand("ipguard.remove")]
private void CmdRemove(ConsoleSystem.Arg arg)
{
if (!arg.IsAdmin && (arg.Player() == null || !permission.UserHasPermission(arg.Player().UserIDString, PermManage)))
{
arg.ReplyWith(Msg("NoPerm"));
return;
}
string entry = arg.GetString(0, "");
if (string.IsNullOrEmpty(entry)) return;
data.Banned.Remove(entry);
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
BuildPrefixes();
arg.ReplyWith(Msg("Removed", null, entry));
}
[ConsoleCommand("ipguard.list")]
private void CmdList(ConsoleSystem.Arg arg)
{
arg.ReplyWith(Msg("List", null, data.Banned.Count));
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
}
}The plugin keeps a configurable list of banned IPs and CIDR ranges and rejects any connecting client whose address matches. OnUserApproved compares ipaddr against each entry; exact strings match directly, while entries containing / are parsed once at startup into a network prefix and mask length and compared bitwise. A match returns a localized rejection message that includes the reason, so the player sees it in the disconnect dialog. Admins manage the list through the ipguard.add and ipguard.remove console commands, and every change is written to oxide/data/IpConnectionGuard. The plugin also logs rejections at a low rate so admins can see what is being blocked without spamming the console.
Common use cases
Whitelist servers
Return a rejection string when
useridis not in a stored whitelist, so only approved Steam accounts ever reach the spawn screen.IP and range bans
Block a proxy or a whole subnet by matching
ipaddragainst stored entries, catching ban evaders who switch Steam accounts.Maintenance windows
While a flag is set, reject everyone except admins (looked up by
userid) so the server stays closed during a wipe or a plugin emergency.
Pitfalls
The player object does not exist yet
There is no
BasePlayerat this point, soBasePlayer.FindByIDand similar calls will return null. Work with the raw Steam ID string instead.Ban checks with a delay
Some external ban services answer asynchronously. Since you cannot wait inside the hook, a delayed check has to run in a timer and kick or reject the player afterwards, not through the return value.
Username is not an identity
usernamecomes from the client and can change or be spoofed. Base every accept/reject decision onuserid, never on the display name.
Build a plugin with OnUserApproved
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnUserApproved
Generate