Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending service stack authentication - populating user session with custom user auth meta data

Tags:

servicestack

I am trying to extend Service Stack's authentication and registration features. I have the authentication and registration working fine, however I need to add some custom data for each user. From Service Stack's documentation and various other posts I found you can add your own data using the MetaData column built into the UserAuth table.

I created a CustomAuthRepository so I can set the meta data property of UserAuth, here is my custom repo:

public class CustomAuthRepository : OrmLiteAuthRepository, IUserAuthRepository
{
    public UserAuth CreateUserAuth(UserAuth newUser, string password)
    {
        newUser.Set(new LoginInfo
        {
            IsActive = false,
            PasswordNeedsReset = true
        });
        return base.CreateUserAuth(newUser, password);
    }
}

This is working great for setting the meta data, I end up with a serialized version of the LoginInfo object in the meta data column of the UserAuth table.

Now what I am trying to do is when a user authenticates I need to change the AuthResponse based on some of that meta data. For example, if a user is not yet activated I want to return an AuthResponse with a property IsActive = get value from custom meta data

I figure I could do this if I can get my custom metadata into the AuthSession. That way in my custom credentials auth provider I could change the response object based on what's in the AuthSession:

public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
    public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
    {
        var customUserAuthSession = (CustomUserAuthSession)session;

        if (!customUserAuthSession.LoginInfo.IsActive)
        {
            return new
            {
                UserName = customUserAuthSession.UserName,
                IsActive = customUserAuthSession.LoginInfo.IsActive
            };
        }

        var isAuthenticated = base.Authenticate(authService, session, request);

        return isAuthenticated;
    } 
}

Am I going about this the right way, or is there a better way to store and retrieve custom meta data?

How can I change the AuthResponse based on a user's custom meta data?

How can I get my custom meta data into the AuthSession?

Edit I am getting closer to what I am trying to do. In my CustomAuthSession OnAuthenticated() method :

    public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
    {
        var customUserAuthSession = (CustomUserAuthSession) session;

        var userAuth = authService.ResolveService<IUserAuthRepository>().GetUserAuth(session, null);
        customUserAuthSession.LoginInfo = userAuth.Get<LoginInfo>();

        authService.SaveSession(customUserAuthSession);

        base.OnAuthenticated(authService, session, tokens, authInfo);
    }

I am refetching the UserAuth and populating the session with the data that I need. Based on the service stack documentation for a custom user session, you need to save the session after you populate it with some custom data. I am doing that but it doesn't seem to be saving. In my CustomCredentialsAuthProvider, Authenticate method, I don't see the custom data I've added to the session.

Edit The problem with my first edit above is that the user gets authenticated, then we get to the CustomAuthSession code where I can check if they are active or not. In the case they are not active I would need to log them out, not ideal.

I found instead that I can do all of this in the Authenticate method of my custom CredentialsAuthProvider.

    public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
    {
        var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
        var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
        var loginInfo = userAuth.Get<LoginInfo>();

        if (!loginInfo.IsActive)
        {
            return new CustomAuthResponse
            {
                UserName = userAuth.UserName,
                ResponseStatus = new ResponseStatus("500"),
                IsActive = loginInfo.IsActive
            };
        }

        var authResponse = (AuthResponse)base.Authenticate(authService, session, request);

        return authResponse;
    }

When the request comes in I can use the username in the request to fetch the UserAuth, and check if the user IsActive or not. If not then I can return some error before Service Stack authenticates them.

I think this works well enough for what I am trying to do. I should be able to return an error to the client saying the user is not active.

If anyone has a cleaner way to do this that would be great.

like image 376
officert Avatar asked Sep 24 '13 14:09

officert


1 Answers

Here is my answer so far. It works and I can do what I am trying to do, but I would love to hear from some of the Service Stack guys as to whether this is the best way to go about this.

To Save custom meta data

Create a new class that subclasses the OrmLiteAuthRepository. In my case I just want to use Service Stack's built in Sql database persistence and have it create the tables needed.

Re-implement the CreateUserAuth method to save any custom metadata :

    public UserAuth CreateUserAuth(UserAuth newUser, string password)
    {
        newUser.Set(new AccountStatus
        {
            IsActive = false,
            PasswordNeedsReset = true
        });
        return base.CreateUserAuth(newUser, password);
    }

Fetching custom meta data

Create a new class that subclasses the CredentialsAuthProvider. Override the Authenticate method.

    public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
    {
        var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
        var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
        var accountStatus= userAuth.Get<AccountStatus>();

        if (!accountStatus.IsActive)
        {
            throw new InvalidOperationException(string.Format("User {0} is not activated.", request.UserName));
        }
        if (!accountStatus.PasswordNeedsReset)
        {
            throw new InvalidOperationException("Your password needs to be reset before you can login.");
        }

        var authResponse = (AuthResponse)base.Authenticate(authService, session, request);

        return authResponse;
    }

When an authentication request comes into this method, fetch the UserAuth and the custom meta data. If the user is inactive, or their password needs to be reset throw an InvalidOperationException with an error message.

In my client application's Login controller I can check the error message coming back from the service and redirect the user to some page saying there account isn't active yet, or their password needs to be reset before they can be authenticated.

like image 171
officert Avatar answered Jan 04 '23 15:01

officert