diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 484942946c..fc1b2eda83 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1088,7 +1088,7 @@ namespace Emby.Server.Implementations
             MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
 
             NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
-            UserManager.AddParts(GetExports<IAuthenticationProvider>());
+            UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
 
             IsoManager.AddParts(GetExports<IIsoMounter>());
         }
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
new file mode 100644
index 0000000000..ae6fe8239a
--- /dev/null
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
+using ServiceStack;
+using TvDbSharper.Dto;
+
+namespace Emby.Server.Implementations.Library
+{
+    public class DefaultPasswordResetProvider : IPasswordResetProvider
+    {
+        public string Name => "Default Password Reset Provider";
+
+        public bool IsEnabled => true;
+
+        private readonly string _passwordResetFileBase;
+        private readonly string _passwordResetFileBaseDir;
+        private readonly string _passwordResetFileBaseName = "passwordreset";
+
+        private IJsonSerializer _jsonSerializer;
+        private IUserManager _userManager;
+
+        public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager)
+        {
+            _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+            _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+            _jsonSerializer = jsonSerializer;
+            _userManager = userManager;
+        }
+
+        public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+        {
+            HashSet<string> usersreset = new HashSet<string>();
+            foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+            {
+                var spr = (SerializablePasswordReset) _jsonSerializer.DeserializeFromFile(typeof(SerializablePasswordReset), resetfile);
+                if (spr.ExpirationDate > DateTime.Now)
+                {
+                    File.Delete(resetfile);
+                }
+                else
+                {
+                    if (spr.Pin == pin)
+                    {
+                        var resetUser = _userManager.GetUserByName(spr.UserName);
+                        if (!string.IsNullOrEmpty(resetUser.Password))
+                        {
+                            await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+                            usersreset.Add(resetUser.Name);
+                        }
+                    }
+                }
+            }
+
+            if (usersreset.Count < 1)
+            {
+                throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+            }
+            else
+            {
+                return new PinRedeemResult
+                {
+                    Success = true,
+                    UsersReset = usersreset.ToArray()
+                };
+            }
+            throw new System.NotImplementedException();
+        }
+
+        public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+        {
+            string pin = new Random().Next(99999999).ToString("00000000",CultureInfo.InvariantCulture);
+            DateTime expireTime = DateTime.Now.AddMinutes(30);
+            string filePath = _passwordResetFileBase + user.Name.ToLowerInvariant() + ".json";
+            SerializablePasswordReset spr = new SerializablePasswordReset
+            {
+                ExpirationDate = expireTime,
+                Pin = pin,
+                PinFile = filePath,
+                UserName = user.Name
+            };
+
+            try
+            {
+                await Task.Run(() => File.WriteAllText(filePath, _jsonSerializer.SerializeToString(spr))).ConfigureAwait(false);
+            }
+            catch (Exception e)
+            {
+                throw new Exception($"Error serializing or writing password reset for {user.Name} to location:{filePath}", e);
+            }
+
+            return new ForgotPasswordResult
+            {
+                Action = ForgotPasswordAction.PinCode,
+                PinExpirationDate = expireTime,
+                PinFile = filePath
+            };
+        }
+
+        private class SerializablePasswordReset : PasswordPinCreationResult
+        {
+            public string Pin { get; set; }
+
+            public string UserName { get; set; }
+        }
+    }
+}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 4cf703add5..500bb8d665 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -79,6 +79,10 @@ namespace Emby.Server.Implementations.Library
         private IAuthenticationProvider[] _authenticationProviders;
         private DefaultAuthenticationProvider _defaultAuthenticationProvider;
 
+        private IPasswordResetProvider[] _passwordResetProviders;
+        private DefaultPasswordResetProvider _defaultPasswordResetProvider;
+        private Dictionary<string, IPasswordResetProvider> _activeResets = new Dictionary<string, IPasswordResetProvider>();
+
         public UserManager(
             ILoggerFactory loggerFactory,
             IServerConfigurationManager configurationManager,
@@ -102,8 +106,6 @@ namespace Emby.Server.Implementations.Library
             _fileSystem = fileSystem;
             ConfigurationManager = configurationManager;
             _users = Array.Empty<User>();
-
-            DeletePinFile();
         }
 
         public NameIdPair[] GetAuthenticationProviders()
@@ -120,11 +122,29 @@ namespace Emby.Server.Implementations.Library
                 .ToArray();
         }
 
-        public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders)
+        public NameIdPair[] GetPasswordResetProviders()
+        {
+            return _passwordResetProviders
+                .Where(i => i.IsEnabled)
+                .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1)
+                .ThenBy(i => i.Name)
+                .Select(i => new NameIdPair
+                {
+                    Name = i.Name,
+                    Id = GetPasswordResetProviderId(i)
+                })
+                .ToArray();
+        }
+
+        public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders,IEnumerable<IPasswordResetProvider> passwordResetProviders)
         {
             _authenticationProviders = authenticationProviders.ToArray();
 
             _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
+
+            _passwordResetProviders = passwordResetProviders.ToArray();
+
+            _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
         }
 
         #region UserUpdated Event
@@ -342,11 +362,21 @@ namespace Emby.Server.Implementations.Library
             return provider.GetType().FullName;
         }
 
+        private static string GetPasswordResetProviderId(IPasswordResetProvider provider)
+        {
+            return provider.GetType().FullName;
+        }
+
         private IAuthenticationProvider GetAuthenticationProvider(User user)
         {
             return GetAuthenticationProviders(user).First();
         }
 
+        private IPasswordResetProvider GetPasswordResetProvider(User user)
+        {
+            return GetPasswordResetProviders(user).First();
+        }
+
         private IAuthenticationProvider[] GetAuthenticationProviders(User user)
         {
             var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
@@ -366,6 +396,25 @@ namespace Emby.Server.Implementations.Library
             return providers;
         }
 
+        private IPasswordResetProvider[] GetPasswordResetProviders(User user)
+        {
+            var passwordResetProviderId = user == null ? null : user.Policy.PasswordResetProviderId;
+
+            var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
+
+            if (!string.IsNullOrEmpty(passwordResetProviderId))
+            {
+                providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray();
+            }
+
+            if (providers.Length == 0)
+            {
+                providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider };
+            }
+
+            return providers;
+        }
+
         private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
         {
             try
@@ -844,159 +893,52 @@ namespace Emby.Server.Implementations.Library
                 Id = Guid.NewGuid(),
                 DateCreated = DateTime.UtcNow,
                 DateModified = DateTime.UtcNow,
-                UsesIdForConfigurationPath = true,
-                //Salt = BCrypt.GenerateSalt()
-            };
-        }
-
-        private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt");
-
-        private string _lastPin;
-        private PasswordPinCreationResult _lastPasswordPinCreationResult;
-        private int _pinAttempts;
-
-        private async Task<PasswordPinCreationResult> CreatePasswordResetPin()
-        {
-            var num = new Random().Next(1, 9999);
-
-            var path = PasswordResetFile;
-
-            var pin = num.ToString("0000", CultureInfo.InvariantCulture);
-            _lastPin = pin;
-
-            var time = TimeSpan.FromMinutes(5);
-            var expiration = DateTime.UtcNow.Add(time);
-
-            var text = new StringBuilder();
-
-            var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty;
-
-            text.AppendLine("Use your web browser to visit:");
-            text.AppendLine(string.Empty);
-            text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html");
-            text.AppendLine(string.Empty);
-            text.AppendLine("Enter the following pin code:");
-            text.AppendLine(string.Empty);
-            text.AppendLine(pin);
-            text.AppendLine(string.Empty);
-
-            var localExpirationTime = expiration.ToLocalTime();
-            // Tuesday, 22 August 2006 06:30 AM
-            text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture));
-
-            File.WriteAllText(path, text.ToString(), Encoding.UTF8);
-
-            var result = new PasswordPinCreationResult
-            {
-                PinFile = path,
-                ExpirationDate = expiration
+                UsesIdForConfigurationPath = true
             };
-
-            _lastPasswordPinCreationResult = result;
-            _pinAttempts = 0;
-
-            return result;
         }
 
         public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
         {
-            DeletePinFile();
-
             var user = string.IsNullOrWhiteSpace(enteredUsername) ?
                 null :
                 GetUserByName(enteredUsername);
 
             var action = ForgotPasswordAction.InNetworkRequired;
-            string pinFile = null;
-            DateTime? expirationDate = null;
 
-            if (user != null && !user.Policy.IsAdministrator)
+            if (user != null && isInNetwork)
             {
-                action = ForgotPasswordAction.ContactAdmin;
+                var passwordResetProvider = GetPasswordResetProvider(user);
+                _activeResets.Add(user.Name, passwordResetProvider);
+                return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false);
             }
             else
             {
-                if (isInNetwork)
+                return new ForgotPasswordResult
                 {
-                    action = ForgotPasswordAction.PinCode;
-                }
-
-                var result = await CreatePasswordResetPin().ConfigureAwait(false);
-                pinFile = result.PinFile;
-                expirationDate = result.ExpirationDate;
+                    Action = action,
+                    PinFile = string.Empty
+                };
             }
-
-            return new ForgotPasswordResult
-            {
-                Action = action,
-                PinFile = pinFile,
-                PinExpirationDate = expirationDate
-            };
         }
 
         public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
         {
-            DeletePinFile();
-
-            var usersReset = new List<string>();
-
-            var valid = !string.IsNullOrWhiteSpace(_lastPin) &&
-                string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) &&
-                _lastPasswordPinCreationResult != null &&
-                _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow;
-
-            if (valid)
+            foreach (var provider in _passwordResetProviders)
             {
-                _lastPin = null;
-                _lastPasswordPinCreationResult = null;
-
-                foreach (var user in Users)
+                var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false);
+                if (result.Success)
                 {
-                    await ResetPassword(user).ConfigureAwait(false);
-
-                    if (user.Policy.IsDisabled)
-                    {
-                        user.Policy.IsDisabled = false;
-                        UpdateUserPolicy(user, user.Policy, true);
-                    }
-                    usersReset.Add(user.Name);
-                }
-            }
-            else
-            {
-                _pinAttempts++;
-                if (_pinAttempts >= 3)
-                {
-                    _lastPin = null;
-                    _lastPasswordPinCreationResult = null;
+                    return result;
                 }
             }
 
             return new PinRedeemResult
             {
-                Success = valid,
-                UsersReset = usersReset.ToArray()
+                Success = false,
+                UsersReset = Array.Empty<string>()
             };
         }
 
-        private void DeletePinFile()
-        {
-            try
-            {
-                _fileSystem.DeleteFile(PasswordResetFile);
-            }
-            catch
-            {
-
-            }
-        }
-
-        class PasswordPinCreationResult
-        {
-            public string PinFile { get; set; }
-            public DateTime ExpirationDate { get; set; }
-        }
-
         public UserPolicy GetUserPolicy(User user)
         {
             var path = GetPolicyFilePath(user);
diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs
index f011e6e417..4109b12bfa 100644
--- a/MediaBrowser.Api/Session/SessionsService.cs
+++ b/MediaBrowser.Api/Session/SessionsService.cs
@@ -245,6 +245,12 @@ namespace MediaBrowser.Api.Session
     {
     }
 
+    [Route("/Auth/PasswordResetProviders", "GET")]
+    [Authenticated(Roles = "Admin")]
+    public class GetPasswordResetProviders : IReturn<NameIdPair[]>
+    {
+    }
+
     [Route("/Auth/Keys/{Key}", "DELETE")]
     [Authenticated(Roles = "Admin")]
     public class RevokeKey
@@ -294,6 +300,11 @@ namespace MediaBrowser.Api.Session
             return _userManager.GetAuthenticationProviders();
         }
 
+        public object Get(GetPasswordResetProviders request)
+        {
+            return _userManager.GetPasswordResetProviders();
+        }
+
         public void Delete(RevokeKey request)
         {
             _sessionManager.RevokeToken(request.Key);
diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
new file mode 100644
index 0000000000..9e5cd88160
--- /dev/null
+++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Users;
+
+namespace MediaBrowser.Controller.Authentication
+{
+    public interface IPasswordResetProvider
+    {
+        string Name { get; }
+        bool IsEnabled { get; }
+        Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork);
+        Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
+    }
+    public class PasswordPinCreationResult
+    {
+        public string PinFile { get; set; }
+        public DateTime ExpirationDate { get; set; }
+    }
+}
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 925d91a375..7f73708931 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -200,8 +200,9 @@ namespace MediaBrowser.Controller.Library
         /// <returns>System.String.</returns>
         string MakeValidUsername(string username);
 
-        void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders);
+        void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
 
         NameIdPair[] GetAuthenticationProviders();
+        NameIdPair[] GetPasswordResetProviders();
     }
 }
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 5415fd5e81..f63ab2bb4c 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -75,6 +75,7 @@ namespace MediaBrowser.Model.Users
 
         public int RemoteClientBitrateLimit { get; set; }
         public string AuthenticationProviderId { get; set; }
+        public string PasswordResetProviderId { get; set; }
 
         public UserPolicy()
         {