Cristóvão
Cristóvão

Reputation: 255

Adding RequestFilter data to Context (Request Scope), Retrieve in Service

I implemented Basic Auth for my services. Since ServiceStack's AuthFeature is strongly coupled with the session concept, I implemented a custom RequestFilter that performs stateless basic auth (credentials go in, on every request). Our auth strategy internally contemplates roles and permissions.

Besides authentication, we need to enforce authorization (e.g., user is manipulating a product that he owns). We are using FluentValidation, for all service validations.

Authorization validations include cross checking auth data with request parameters. Question is, where should I place the auth data produced in the BasicAuthRequestFilter? Should I key value pair it in cache, associating, for instance, RequestContext (or any other object that uniquely identifies the request scope) with an Authentication object?

I could just plug the AuthData in the Request Dto, which is available directly at the RequestFilter, however this would mess up our service contract design. We define dtos in a separate DLL, where only service input/output details are defined.

Any suggestions? Thanks in advance.

Upvotes: 2

Views: 247

Answers (1)

Scott
Scott

Reputation: 21501

I too use my own custom authentication mechanism and make custom role information available to my service. I do this by authenticating the request in a custom ServiceRunner which can then pass the information directly to my custom Service base. This ultimately means accessing information about the users permissions is exceptionally easy.

Create a custom ServiceRunner:

public class ServiceRunner<T> : ServiceStack.ServiceHost.ServiceRunner<T>
{
    public ServiceRunner(IAppHost appHost, ActionContext actionContext) : base(appHost, actionContext)
    {
    }

    public override object Execute(IRequestContext requestContext, object instance, T request)
    {
        // Check if the instance is of type AuthenticatedBase
        var authenticatedBase = instance as AuthenticatedBase;

        // If the request is not using the AuthenticatedBase, then allow it to run, as normal.
        if(authenticatedBase == null)
            return base.Execute(requestContext, instance, request);

        /* 
         * Authentication required. Do you authorization check here.
         * i.e.
         * var authorization = requestContext.GetHeader("Authorization");
         * bool authorised = ... some condition;
        */

        /* You have access to your service base so if you injected the Db connection
         * in you app config using IoC, then you can access the Db here.
         * i.e.
         * authenticatedBase.Db
         */

         /*
          * Not authorized?
          * throw new UnauthorizedException();
          */

         /*
          * If authorized:
          * Then simple set the details about their permissions
          */

         authenticatedBase.AuthData = new AuthData { Id = 123, Roles = [], Username = "" };

        // Pass back the authenticated base
        return base.Execute(requestContext, authenticatedBase, request);
    }
}

Configure you application to use it by adding this to your AppHost:

public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
    return new ServiceRunner<TRequest>(this, actionContext);
}

Create a custom class to hold your auth data i.e. the user session information, such as:

public class AuthData
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int[] Roles { get; set; }
    ...
}

Then create a custom service base

public class AuthenticatedBase : Service
{
    public AuthData AuthData { get; set; }
}

To then use the AuthData in the service is simply a case of extending AuthenticatedBase.

public class CustomerHandler : AuthenticatedBase
{
    public object Get(ListCustomers request)
    {
        // You can access the AuthData now in the handler
        var roles = AuthData.Role; // Check they have the role required to list customers
        ...
    }
}

You are probably wondering why go to all the trouble of using the ServiceRunner over a RequestFilter but the main advantage is it gives direct access to the instance of the Service base, which isn't available to a RequestFilter.

The RequestFilters are run before the Service base is instantiated, so you can't populate it from there. See order of operations for more information.

By having access to the ServiceBase we can populate values (in this case AuthData) and we have access to our injected dependancies such as the database connection.

I hope you find this useful. You should be able to copy most of your existing RequestFilter into the service runner. If you need any further help with this just let me know.


Update to support Attributes:

Since you are unable to avoid using the attribute method to handle your authentication needs you can still use this method:

  • Continue doing your authentication and access filtering the way you were before.
  • In your existing authentication mechanism use req.Items.Add to set the AuthData i.e. Where req is your request object

    req.Items.Add("AuthData", new AuthData { Username = "", Roles = [] ... });
    
  • Then access your AuthData item in your service base:

    public class AuthenticatedBase : Service
    {
        public AuthData AuthData 
        { 
            get { return base.Request.Items["AuthData"] as AuthData; }
        }
    }    
    

Upvotes: 4

Related Questions