Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 417
Khalid Abuhakmeh Avatar asked Jan 14 '13 20:01

Khalid Abuhakmeh


2 Answers

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....

like image 121
Gavin Faux Avatar answered Oct 31 '22 23:10

Gavin Faux


I'm just starting with ServiceStack and I needed exactly the same thing - and I managed to get it to work today.

The absolute bare minimum for logging in users via Basic Auth is this:

using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;

public class CustomBasicAuthProvider : BasicAuthProvider
{
    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        // here, you can get the user data from your database instead
        if (userName == "MyUser" && password == "123")
        {
            return true;
        }

        return false;
    }
}

...and register it in the AppHost:

Plugins.Add(new AuthFeature(() => new CustomUserSession(),
    new IAuthProvider[] {
        new CustomBasicAuthProvider()
    }) { HtmlRedirect = null });

That's all!


Another possible solution would be to use the default BasicAuthProvider and provide an own implementation of IUserAuthRepository instead.

I can show you an example of this as well, if you're interested.


EDIT:

Here's the bare minimum IUserAuthRepository - just inherit from InMemoryAuthRepository and override TryAuthenticate:

using ServiceStack.ServiceInterface.Auth;

public class CustomAuthRepository : InMemoryAuthRepository
{
    public override bool TryAuthenticate(string userName, string password, out UserAuth userAuth)
    {
        userAuth = null;

        if (userName == "MyUser" && password == "123")
        {
            userAuth = new UserAuth();
            return true;
        }

        return false;
    }
}

...and register it in the AppHost:

container.Register<IUserAuthRepository>(r => new CustomAuthRepository());

Of course, you need to register one of the default AuthProviders (Basic, Credentials, whatever) as well.

like image 3
Christian Specht Avatar answered Oct 31 '22 23:10

Christian Specht