diff --git a/DnsServerCore/Auth/UserSession.cs b/DnsServerCore/Auth/UserSession.cs
new file mode 100644
index 00000000..14d5d1aa
--- /dev/null
+++ b/DnsServerCore/Auth/UserSession.cs
@@ -0,0 +1,179 @@
+/*
+Technitium DNS Server
+Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Security.Cryptography;
+using TechnitiumLibrary.IO;
+using TechnitiumLibrary.Net;
+
+namespace DnsServerCore.Auth
+{
+ enum UserSessionType : byte
+ {
+ Unknown = 0,
+ Standard = 1,
+ ApiToken = 2
+ }
+
+ class UserSession : IComparable
+ {
+ #region variables
+
+ static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
+
+ readonly string _token;
+ readonly UserSessionType _type;
+ readonly string _tokenName;
+ readonly User _user;
+ DateTime _lastSeen;
+ IPAddress _lastSeenRemoteAddress;
+ string _lastSeenUserAgent;
+
+ #endregion
+
+ #region constructor
+
+ public UserSession(UserSessionType type, string tokenName, User user, IPAddress remoteAddress, string lastSeenUserAgent)
+ {
+ if ((tokenName is not null) && (tokenName.Length > 255))
+ throw new ArgumentOutOfRangeException(nameof(tokenName), "Token name length cannot exceed 255 characters.");
+
+ byte[] tokenBytes = new byte[32];
+ _rng.GetBytes(tokenBytes);
+ _token = Convert.ToHexString(tokenBytes).ToLower();
+
+ _type = type;
+ _tokenName = tokenName;
+ _user = user;
+ _lastSeen = DateTime.UtcNow;
+ _lastSeenRemoteAddress = remoteAddress;
+ _lastSeenUserAgent = lastSeenUserAgent;
+
+ if ((_lastSeenUserAgent is not null) && (_lastSeenUserAgent.Length > 255))
+ _lastSeenUserAgent = _lastSeenUserAgent.Substring(0, 255);
+ }
+
+ public UserSession(BinaryReader bR, AuthManager authManager)
+ {
+ switch (bR.ReadByte())
+ {
+ case 1:
+ _token = bR.ReadShortString();
+ _type = (UserSessionType)bR.ReadByte();
+
+ _tokenName = bR.ReadShortString();
+ if (_tokenName.Length == 0)
+ _tokenName = null;
+
+ _user = authManager.GetUser(bR.ReadShortString());
+ _lastSeen = bR.ReadDateTime();
+ _lastSeenRemoteAddress = IPAddressExtension.ReadFrom(bR);
+
+ _lastSeenUserAgent = bR.ReadShortString();
+ if (_lastSeenUserAgent.Length == 0)
+ _lastSeenUserAgent = null;
+
+ break;
+
+ default:
+ throw new InvalidDataException("Invalid data or version not supported.");
+ }
+ }
+
+ #endregion
+
+ #region public
+
+ public void UpdateLastSeen(IPAddress remoteAddress, string lastSeenUserAgent)
+ {
+ _lastSeen = DateTime.UtcNow;
+ _lastSeenRemoteAddress = remoteAddress;
+ _lastSeenUserAgent = lastSeenUserAgent;
+
+ if ((_lastSeenUserAgent is not null) && (_lastSeenUserAgent.Length > 255))
+ _lastSeenUserAgent = _lastSeenUserAgent.Substring(0, 255);
+ }
+
+ public bool HasExpired()
+ {
+ if (_type == UserSessionType.ApiToken)
+ return false;
+
+ if (_user.SessionTimeoutSeconds == 0)
+ return false;
+
+ return _lastSeen.AddSeconds(_user.SessionTimeoutSeconds) < DateTime.UtcNow;
+ }
+
+ public void WriteTo(BinaryWriter bW)
+ {
+ bW.Write((byte)1);
+ bW.WriteShortString(_token);
+ bW.Write((byte)_type);
+
+ if (_tokenName is null)
+ bW.Write((byte)0);
+ else
+ bW.WriteShortString(_tokenName);
+
+ bW.WriteShortString(_user.Username);
+ bW.Write(_lastSeen);
+ _lastSeenRemoteAddress.WriteTo(bW);
+
+ if (_lastSeenUserAgent is null)
+ bW.Write((byte)0);
+ else
+ bW.WriteShortString(_lastSeenUserAgent);
+ }
+
+ public int CompareTo(UserSession other)
+ {
+ return other._lastSeen.CompareTo(_lastSeen);
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Token
+ { get { return _token; } }
+
+ public UserSessionType Type
+ { get { return _type; } }
+
+ public string TokenName
+ { get { return _tokenName; } }
+
+ public User User
+ { get { return _user; } }
+
+ public DateTime LastSeen
+ { get { return _lastSeen; } }
+
+ public IPAddress LastSeenRemoteAddress
+ { get { return _lastSeenRemoteAddress; } }
+
+ public string LastSeenUserAgent
+ { get { return _lastSeenUserAgent; } }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/UserSession.cs b/DnsServerCore/UserSession.cs
deleted file mode 100644
index 961baa01..00000000
--- a/DnsServerCore/UserSession.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-Technitium DNS Server
-Copyright (C) 2019 Shreyas Zare (shreyas@technitium.com)
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-
-*/
-
-using System;
-
-namespace DnsServerCore
-{
- class UserSession
- {
- #region variables
-
- const int SESSION_TIMEOUT = 30 * 60 * 1000; //30 mins
-
- readonly string _username;
- DateTime _lastSeen;
-
- #endregion
-
- #region constructor
-
- public UserSession(string username)
- {
- _username = username;
- _lastSeen = DateTime.UtcNow;
- }
-
- #endregion
-
- #region public
-
- public void UpdateLastSeen()
- {
- _lastSeen = DateTime.UtcNow;
- }
-
- public bool HasExpired()
- {
- return _lastSeen.AddMilliseconds(SESSION_TIMEOUT) < DateTime.UtcNow;
- }
-
- #endregion
-
- #region properties
-
- public string Username
- { get { return _username; } }
-
- #endregion
- }
-}