servicestackapi-keyservicestack-auth

ServiceStack Custom Credentials Auth with DB Stored Api Keys


Right now, we're authenticating our users with this:

public class WindowsAuthProvider : CredentialsAuthProvider
{
    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "OurDomain"))
        {
            // TODO make sure user record exists in custom DB tables as well
            return pc.ValidateCredentials(userName, password);
        }
    }

    public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
    {
        return base.OnAuthenticated(authService, session, tokens, authInfo);
    }
}

Which works great when using the JsonServiceClient.

We have some legacy code written in Visual FoxPro which wants to call some of the authenticated functions in ServiceStack... to accommodate this, we'd like to also allow Api Keys. We want the API Keys to be stored in SQL Server to avoid issues if the process stops / restarts. So, the client would authenticate with domain credentials, then generate an API key for subsequent calls which would be stored in the database (ideally just using the table servicestack can create (dbo.ApiKey).

If we were to set this per the docs:

container.Register<IAuthRepository>(c => new OrmLiteAuthRepository(dbFactory));

We get an error on the OnAuthenticated function above telling us we should call Init()... like its trying to also create the user tables. So I'm not sure how to allow DB stored API Keys, along with custom authentication that relies on both active directory as well as our custom tables for users and roles.

Instead of inheriting from CredentialsAuthProvider, maybe its better to register a custom IUserAuthRepository and IManageRoles?


Solution

  • The API Key AuthProvider needs to be registered in your AuthFeature, e.g:

    Plugins.Add(new AuthFeature(...,
        new IAuthProvider[] {
            new ApiKeyAuthProvider(AppSettings),
            new WindowsAuthProvider(AppSettings),
            //...
        }));
    

    Which requires a IAuthRepository like you're doing:

    container.Register<IAuthRepository>(c => 
        new OrmLiteAuthRepository(dbFactory));
    

    Any AuthProvider that requires creating a back-end tables or other schema requires that its schema is initialized on Startup which you can do with:

    container.Resolve<IAuthRepository>().InitSchema();
    

    It's safe to always call InitSchema() as it only creates missing tables or is otherwise ignored for AuthRepositories that don't require creating a schema.

    An issue you're running into is that you've registered an IAuthRepository and are inheriting a CredentialsAuthProvider which you don't want to use it in so you can't call CredentialsAuthProvider.OnAuthenticated() since it will save the User Auth info to the repository if it exists.

    So you'll need to provide a custom implement without calling base.OnAuthenticated(), e.g:

    public class WindowsAuthProvider : CredentialsAuthProvider
    {
        public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
        {
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "OurDomain"))
            {
                // TODO make sure user record exists in custom DB tables as well
                return pc.ValidateCredentials(userName, password);
            }
        }
    
        public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            try
            {
                session.IsAuthenticated = true;
                session.OnAuthenticated(authService, session, tokens, authInfo);
                AuthEvents.OnAuthenticated(authService.Request, session, authService, tokens, authInfo);
            }
            finally
            {
                this.SaveSession(authService, session, SessionExpiry);
            }
    
            return null;
        }
    }