Skip to content
RustPluginGenerator

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.

Signature
object OnUserApproved(string username, string userid, string ipaddr)

Parameters

Hook parameters
ParameterTypeDescription
usernamestringThe 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.
useridstringThe Steam ID of the connecting client as a string, e.g. "76561198000000000". Use it to look up bans, whitelists or stored player data.
ipaddrstringThe 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

IpConnectionGuard.cs
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 userid is 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 ipaddr against 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 BasePlayer at this point, so BasePlayer.FindByID and 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

    username comes from the client and can change or be spoofed. Base every accept/reject decision on userid, never on the display name.

Build a plugin with OnUserApproved

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Whitelist servers using the OnUserApproved hook, with a config file and a permission
Generate this plugin

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