diff --git a/src/NATS.Server/Auth/AuthService.cs b/src/NATS.Server/Auth/AuthService.cs
new file mode 100644
index 0000000..ba348d5
--- /dev/null
+++ b/src/NATS.Server/Auth/AuthService.cs
@@ -0,0 +1,131 @@
+using System.Security.Cryptography;
+
+namespace NATS.Server.Auth;
+
+///
+/// Central authentication orchestrator that builds the appropriate authenticators
+/// from NatsOptions and tries them in priority order matching the Go server:
+/// NKeys > Users > Token > SimpleUserPassword.
+/// Reference: golang/nats-server/server/auth.go — checkClientAuth, configureAuthentication.
+///
+public sealed class AuthService
+{
+ private readonly List _authenticators;
+ private readonly string? _noAuthUser;
+ private readonly Dictionary? _usersMap;
+
+ public bool IsAuthRequired { get; }
+ public bool NonceRequired { get; }
+
+ private AuthService(List authenticators, bool authRequired, bool nonceRequired,
+ string? noAuthUser, Dictionary? usersMap)
+ {
+ _authenticators = authenticators;
+ IsAuthRequired = authRequired;
+ NonceRequired = nonceRequired;
+ _noAuthUser = noAuthUser;
+ _usersMap = usersMap;
+ }
+
+ public static AuthService Build(NatsOptions options)
+ {
+ var authenticators = new List();
+ var authRequired = false;
+ var nonceRequired = false;
+ Dictionary? usersMap = null;
+
+ // Priority order (matching Go): NKeys > Users > Token > SimpleUserPassword
+
+ if (options.NKeys is { Count: > 0 })
+ {
+ authenticators.Add(new NKeyAuthenticator(options.NKeys));
+ authRequired = true;
+ nonceRequired = true;
+ }
+
+ if (options.Users is { Count: > 0 })
+ {
+ authenticators.Add(new UserPasswordAuthenticator(options.Users));
+ authRequired = true;
+ usersMap = new Dictionary(StringComparer.Ordinal);
+ foreach (var u in options.Users)
+ usersMap[u.Username] = u;
+ }
+
+ if (!string.IsNullOrEmpty(options.Authorization))
+ {
+ authenticators.Add(new TokenAuthenticator(options.Authorization));
+ authRequired = true;
+ }
+
+ if (!string.IsNullOrEmpty(options.Username) && !string.IsNullOrEmpty(options.Password))
+ {
+ authenticators.Add(new SimpleUserPasswordAuthenticator(options.Username, options.Password));
+ authRequired = true;
+ }
+
+ return new AuthService(authenticators, authRequired, nonceRequired, options.NoAuthUser, usersMap);
+ }
+
+ public AuthResult? Authenticate(ClientAuthContext context)
+ {
+ if (!IsAuthRequired)
+ return new AuthResult { Identity = string.Empty };
+
+ foreach (var authenticator in _authenticators)
+ {
+ var result = authenticator.Authenticate(context);
+ if (result != null)
+ return result;
+ }
+
+ if (_noAuthUser != null && IsNoCredentials(context))
+ return ResolveNoAuthUser();
+
+ return null;
+ }
+
+ private static bool IsNoCredentials(ClientAuthContext context)
+ {
+ var opts = context.Opts;
+ return string.IsNullOrEmpty(opts.Username)
+ && string.IsNullOrEmpty(opts.Password)
+ && string.IsNullOrEmpty(opts.Token)
+ && string.IsNullOrEmpty(opts.Nkey)
+ && string.IsNullOrEmpty(opts.Sig);
+ }
+
+ private AuthResult? ResolveNoAuthUser()
+ {
+ if (_noAuthUser == null)
+ return null;
+
+ if (_usersMap != null && _usersMap.TryGetValue(_noAuthUser, out var user))
+ {
+ return new AuthResult
+ {
+ Identity = user.Username,
+ AccountName = user.Account,
+ Permissions = user.Permissions,
+ Expiry = user.ConnectionDeadline,
+ };
+ }
+
+ return new AuthResult { Identity = _noAuthUser };
+ }
+
+ public byte[] GenerateNonce()
+ {
+ Span raw = stackalloc byte[11];
+ RandomNumberGenerator.Fill(raw);
+ return raw.ToArray();
+ }
+
+ public string EncodeNonce(byte[] nonce)
+ {
+ return Convert.ToBase64String(nonce)
+ .TrimEnd('=')
+ .Replace('+', '-')
+ .Replace('/', '_');
+ }
+}
diff --git a/tests/NATS.Server.Tests/AuthServiceTests.cs b/tests/NATS.Server.Tests/AuthServiceTests.cs
new file mode 100644
index 0000000..fd7a15e
--- /dev/null
+++ b/tests/NATS.Server.Tests/AuthServiceTests.cs
@@ -0,0 +1,172 @@
+using NATS.Server.Auth;
+using NATS.Server.Protocol;
+
+namespace NATS.Server.Tests;
+
+public class AuthServiceTests
+{
+ [Fact]
+ public void IsAuthRequired_false_when_no_auth_configured()
+ {
+ var service = AuthService.Build(new NatsOptions());
+ service.IsAuthRequired.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void IsAuthRequired_true_when_token_configured()
+ {
+ var service = AuthService.Build(new NatsOptions { Authorization = "mytoken" });
+ service.IsAuthRequired.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void IsAuthRequired_true_when_username_configured()
+ {
+ var service = AuthService.Build(new NatsOptions { Username = "admin", Password = "pass" });
+ service.IsAuthRequired.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void IsAuthRequired_true_when_users_configured()
+ {
+ var opts = new NatsOptions
+ {
+ Users = [new User { Username = "alice", Password = "secret" }],
+ };
+ var service = AuthService.Build(opts);
+ service.IsAuthRequired.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void IsAuthRequired_true_when_nkeys_configured()
+ {
+ var opts = new NatsOptions
+ {
+ NKeys = [new NKeyUser { Nkey = "UABC" }],
+ };
+ var service = AuthService.Build(opts);
+ service.IsAuthRequired.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Authenticate_succeeds_when_no_auth_required()
+ {
+ var service = AuthService.Build(new NatsOptions());
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions { Token = "anything" },
+ Nonce = [],
+ };
+
+ var result = service.Authenticate(ctx);
+ result.ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void Authenticate_token_success()
+ {
+ var service = AuthService.Build(new NatsOptions { Authorization = "mytoken" });
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions { Token = "mytoken" },
+ Nonce = [],
+ };
+
+ var result = service.Authenticate(ctx);
+ result.ShouldNotBeNull();
+ result.Identity.ShouldBe("token");
+ }
+
+ [Fact]
+ public void Authenticate_token_failure()
+ {
+ var service = AuthService.Build(new NatsOptions { Authorization = "mytoken" });
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions { Token = "wrong" },
+ Nonce = [],
+ };
+
+ service.Authenticate(ctx).ShouldBeNull();
+ }
+
+ [Fact]
+ public void Authenticate_simple_user_password_success()
+ {
+ var service = AuthService.Build(new NatsOptions { Username = "admin", Password = "pass" });
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions { Username = "admin", Password = "pass" },
+ Nonce = [],
+ };
+
+ var result = service.Authenticate(ctx);
+ result.ShouldNotBeNull();
+ result.Identity.ShouldBe("admin");
+ }
+
+ [Fact]
+ public void Authenticate_multi_user_success()
+ {
+ var opts = new NatsOptions
+ {
+ Users = [
+ new User { Username = "alice", Password = "secret1" },
+ new User { Username = "bob", Password = "secret2" },
+ ],
+ };
+ var service = AuthService.Build(opts);
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions { Username = "bob", Password = "secret2" },
+ Nonce = [],
+ };
+
+ var result = service.Authenticate(ctx);
+ result.ShouldNotBeNull();
+ result.Identity.ShouldBe("bob");
+ }
+
+ [Fact]
+ public void NoAuthUser_fallback_when_no_creds()
+ {
+ var opts = new NatsOptions
+ {
+ Users = [
+ new User { Username = "default", Password = "unused" },
+ ],
+ NoAuthUser = "default",
+ };
+ var service = AuthService.Build(opts);
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions(),
+ Nonce = [],
+ };
+
+ var result = service.Authenticate(ctx);
+ result.ShouldNotBeNull();
+ result.Identity.ShouldBe("default");
+ }
+
+ [Fact]
+ public void NKeys_tried_before_users()
+ {
+ var opts = new NatsOptions
+ {
+ NKeys = [new NKeyUser { Nkey = "UABC" }],
+ Users = [new User { Username = "alice", Password = "secret" }],
+ };
+ var service = AuthService.Build(opts);
+
+ var ctx = new ClientAuthContext
+ {
+ Opts = new ClientOptions { Username = "alice", Password = "secret" },
+ Nonce = [],
+ };
+
+ var result = service.Authenticate(ctx);
+ result.ShouldNotBeNull();
+ result.Identity.ShouldBe("alice");
+ }
+}