Search code examples
c#servicestackbasic-authentication

ServiceStack Authentication with Existing Database


I've been looking at ServiceStack and I'm trying to understand how to use BasicAuthentication on a service with an existing database. I would like to generate a public key (username) and secret key (password) and put that in an existing user record. The user would then pass that to the ServiceStack endpoint along with their request.

What do I need to implement in the ServiceStack stack to get this working?

I have looked at both IUserAuthRepository and CredentialsAuthProvider base class and it looks like I should just implement IUserAuthRepository on top of my existing database tables.

I am also trying to figure out what is the bare minimum I should implement to get authentication working. I will not be using the service to Add or Update user access to the Service, but instead using a separate web application.

Any help and past experiences are greatly appreciated.


Solution

  • Example of authenticating against an existing database (in this case via Umbraco/ASP.NET membership system). 1) Create your AuthProvider (forgive the verbose code, and note you don't have to override TryAuthenticate too, this is done here to check if the user is a member of specific Umbraco application aliases):

    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Web.Security;
    
    using ServiceStack.Configuration;
    using ServiceStack.Logging;
    using ServiceStack.ServiceInterface;
    using ServiceStack.ServiceInterface.Auth;
    using ServiceStack.WebHost.Endpoints;
    
    using umbraco.BusinessLogic;
    using umbraco.providers;
    
    public class UmbracoAuthProvider : CredentialsAuthProvider
    {
    
        public UmbracoAuthProvider(IResourceManager appSettings)
        {
            this.Provider = "umbraco";
        }
    
        private UmbracoAuthConfig AuthConfig
        {
            get
            {
                return EndpointHost.AppHost.TryResolve<UmbracoAuthConfig>();
            }
        }
    
        public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            ILog log = LogManager.GetLogger(this.GetType());
            var membershipProvider = (UsersMembershipProvider)Membership.Providers["UsersMembershipProvider"];
    
            if (membershipProvider == null)
            {
                log.Error("UmbracoAuthProvider.OnAuthenticated - NullReferenceException - UsersMembershipProvider");
                session.IsAuthenticated = false;
                return;
            }
    
            MembershipUser user = membershipProvider.GetUser(session.UserAuthName, false);
    
            if (user == null)
            {
                log.ErrorFormat(
                    "UmbracoAuthProvider.OnAuthenticated - GetMembershipUser failed - {0}", session.UserAuthName);
                session.IsAuthenticated = false;
                return;
            }
    
            if (user.ProviderUserKey == null)
            {
                log.ErrorFormat(
                    "UmbracoAuthProvider.OnAuthenticated - ProviderUserKey failed - {0}", session.UserAuthName);
                session.IsAuthenticated = false;
                return;
            }
    
            User umbracoUser = User.GetUser((int)user.ProviderUserKey);
    
            if (umbracoUser == null || umbracoUser.Disabled)
            {
                log.WarnFormat(
                    "UmbracoAuthProvider.OnAuthenticated - GetUmbracoUser failed - {0}", session.UserAuthName);
                session.IsAuthenticated = false;
                return;
            }
    
            session.UserAuthId = umbracoUser.Id.ToString(CultureInfo.InvariantCulture);
            session.Email = umbracoUser.Email;
            session.DisplayName = umbracoUser.Name;
            session.IsAuthenticated = true;
            session.Roles = new List<string>();
            if (umbracoUser.UserType.Name == "Administrators")
            {
                session.Roles.Add(RoleNames.Admin);
            }
    
            authService.SaveSession(session);
            base.OnAuthenticated(authService, session, tokens, authInfo);
        }
    
        public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
        {
            ILog log = LogManager.GetLogger(this.GetType());
            var membershipProvider = (UsersMembershipProvider)Membership.Providers["UsersMembershipProvider"];
    
            if (membershipProvider == null)
            {
                log.Error("UmbracoAuthProvider.TryAuthenticate - NullReferenceException - UsersMembershipProvider");
                return false;
            }
    
            if (!membershipProvider.ValidateUser(userName, password))
            {
                log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - ValidateUser failed - {0}", userName);
                return false;
            }
    
            MembershipUser user = membershipProvider.GetUser(userName, false);
    
            if (user == null)
            {
                log.ErrorFormat("UmbracoAuthProvider.TryAuthenticate - GetMembershipUser failed - {0}", userName);
                return false;
            }
    
            if (user.ProviderUserKey == null)
            {
                log.ErrorFormat("UmbracoAuthProvider.TryAuthenticate - ProviderUserKey failed - {0}", userName);
                return false;
            }
    
            User umbracoUser = User.GetUser((int)user.ProviderUserKey);
    
            if (umbracoUser == null || umbracoUser.Disabled)
            {
                log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - GetUmbracoUser failed - {0}", userName);
                return false;
            }
    
            if (umbracoUser.UserType.Name == "Administrators"
                || umbracoUser.GetApplications()
                              .Any(app => this.AuthConfig.AllowedApplicationAliases.Any(s => s == app.alias)))
            {
                return true;
            }
    
            log.WarnFormat("UmbracoAuthProvider.TryAuthenticate - AllowedApplicationAliases failed - {0}", userName);
    
            return false;
        }
    }
    
    public class UmbracoAuthConfig
    {
    
        public UmbracoAuthConfig(IResourceManager appSettings)
        {
            this.AllowedApplicationAliases = appSettings.GetList("UmbracoAuthConfig.AllowedApplicationAliases").ToList();
        }
    
        public List<string> AllowedApplicationAliases { get; private set; }
    
    }
    

    2) Register provider via usual AppHost Configure method:

        public override void Configure(Container container)
        {
            // .... some config code omitted....
    
            var appSettings = new AppSettings();
            AppConfig = new AppConfig(appSettings);
            container.Register(AppConfig);
    
            container.Register<ICacheClient>(new MemoryCacheClient());
    
            container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
    
            this.Plugins.Add(
                new AuthFeature(
                    // using a custom AuthUserSession here as other checks performed here, e.g. validating Google Apps domain if oAuth enabled/plugged in.
                    () => new CustomAuthSession(), 
                    new IAuthProvider[] { new UmbracoAuthProvider(appSettings) 
                                        }) {
                                              HtmlRedirect = "/api/login" 
                                           });
    
    }
    

    3) Can now authenticate against existing Umbraco database @ yourapidomain/auth/umbraco, using Umbraco to manage users/access to API. No need to implement extra user keys/secrets or BasicAuthentication, unless you really want to....