Authentication outside request scope

Hi,

We are trying to set up an “audit logging” system which works as follows. As part of the business logic, the application can write specific log data to a queue, which will be passed on to a real message queue (IMessageService). We then have a dedicated Service that handles messages from the queue, does some further processing, and writes the result to the database.

However, we are also in the process of applying a modular architecture to our application. Each module exposes its contract via ServiceStack DTOs, and can be accessed either directly via HTTP, or by other modules via the in-process Gateway.

The problem is now that processing the audit logs may require data from another module, but that module currently always requires authentication (because the services are marked with [Authenticate]). One simple solution is to pass the session in the queue message (as described here: Messaging API), but that’s not really correct, because the service that processes the messages shouldn’t have the same restrictions/permissions as the user that triggered the message.

Here is some minimal example code:

public class AuditLogMessage
{
    public required int UserId { get; set; }
    public required AuditLog Data { get; set; }
}


// Somewhere in the app...
_messageProducer.Publish(new AuditLogMessage {...});


public class AuditLogMessageService : Service
{
    public async Task Any(AuditLogMessage request)
    {
        // Fetch some extra info about the user
        var user = await Gateway.SendAsync(new GetUser { Id = request.UserId });
        // ...
    }
}


[Authenticate(Provider = "credentials")]
public class UserService : Service
{
    [RequiredPermission("UserGet")]
    public async Task<object> Get(GetUser request)
    {
        // ...
    }
}

In summary, AuditLogMessageService needs some way to call other modules, while bypassing the usual authentication. Are there any best practices for this?

First of all I would replace Request Filter and action filter attributes, i.e. instead of [RequiredPermission("UserGet")] on the method use declaration validation attributes on the Request DTO:

[ValidateHasPermission("UserGet")]
public class GetUser {}

and for your Service use:

[ValidateIsAuthenticated]
public class OtherRequiresAuthApi {}

As for by passing Authentication you would need to call the Service directly with Resolve Service, e.g:

using var userService = ResolveService<UserService>();
var response = userService.Get(new GetUser { Id = request.UserId });

Otherwise you can move the shared logic in an extension method, e.g. if it uses the DB, you can add a GetUser(this IDbConnection db, string userId) ext method. If it requires multiple dependencies I’d use a reusable command.

Thanks for your reply.

One of the reasons we’re currently using the Gateway is that we only need to depend on a module’s contract (DTOs), while the Service that handles the DTOs is defined elsewhere. It’s a nice way of decoupling an interface from its implementation, which we wouldn’t have if we used the UserService or DB directly.

The commands feature sounds interesting, but if I look at the example from the docs, it seems like this also doesn’t decouple interface and implementation? There is a “request DTO” CreateTodo and a “handler” AddTodoCommand, but you still need to specify the concrete type AddTodoCommand to be able to execute the command?

Execution is decoupled in that you can use RDBMS Background Jobs to execute commands in a serialized RDBMS Queue and executed in a managed background thread with custom execution behavior.

But it needs to know which command to resolve from the IOC that should be executed. It’s not like APIs which map 1 Request DTO to 1 Service implementation. Command DTOs are not unique and can be reused across multiple commands.