ServiceStack v8.3!

We’ve got a couple of exciting features in this release opening ServiceStack up to new use-cases with the potential of changing the internal code-architecture of ServiceStack Apps, especially those benefiting from the value and simplicity of running SQLite on the Server.

Commands Feature

Much of ServiceStack has been focused on providing a productive API First Development experience around your System’s external APIs. Not much attention has been given to internal implementations of APIs as you can use anything that fulfils its service contract by returning the APIs populated Response DTO.

How code-bases are structured is largely a matter of developer preference, however we believe we can add value in this area with the new appealing managed Commands Feature .

Which is predominantly a pattern to structure an encapsulated unit of logic with the dependencies it require implementing the simple:

public interface IAsyncCommand<in T>
{
    Task ExecuteAsync(T request);
}

Here’s a simple example:

public class AddTodoCommand(ILogger<AddTodoCommand> log, IDbConnection db) 
    : IAsyncCommand<CreateTodo>
{
    public async Task ExecuteAsync(CreateTodo request)
    {
        var newTodo = request.ConvertTo<Todo>();
        newTodo.Id = await db.InsertAsync(newTodo, selectIdentity:true);
        log.LogDebug("Created Todo {Id}: {Text}", newTodo.Id, newTodo.Text);
    }
}

In addition to auto wiring all commands by default, it supports Auto Retry with the [Retry] attribute as well as an Admin UI which adds observability around commands including timings, exceptions and a rolling list of latest commands executed.

You can think of them like internal application blocks that Admin Users will also be able to execute from the Explore tab:

So they’re a good option for making internal functionality that you only want available to Admin users.

.NET 8 API Keys

As we continue to embrace and natively integrate with ASP.NET Core’s .NET 8 platform, we’ve reimplemented the last major feature missing from ServiceStack Auth - support for API Keys!

Redesigning API Keys

Building on our experience with API Keys in previous versions of ServiceStack, we’ve taken the opportunity to redesign how API Keys work to provide a more flexible and powerful way to manage access control for your APIs.

The existing API Key Auth Provider was implemented as another Auth Provider that provided another way to authenticate a single user. The consequences of this was:

  • Initial API Request was slow as it required going through the Authentication workflow to authenticate the user and setup authentication for that request
  • No support for fine-grained access control as API Keys had same access as the authenticated user
  • API Keys had to be associated with a User which was unnecessary for machine-to-machine communication

Given the primary use-case for API Keys is for machine-to-machine communication where the client isn’t a User, nor do they want systems using their API Keys to have access to their User Account, we’ve changed how API Keys work in .NET 8.

It also includes Admin UIs to manage non-user API Keys on the API Keys Menu:

As well as an Admin UI to manage User API Keys at the bottom of the Edit User Admin UI:

All .NET 8 Identity Auth Tailwind Templates also include a way for Users to create their own API Keys:

Simple Auth for .NET 8 Micro Services

With ServiceStack now fully integrated with ASP.NET Identity Auth, our latest .NET 8 Tailwind Templates offer a full-featured Auth Configuration complete with User Registration, Login, Password Recovery, Two Factory Auth, and more.

Whilst great for Web Applications that need it, it neglects the class of Apps which don’t need User Auth and the additional complexity it brings inc. Identity and Password Management, EF Migrations, Token Expirations, OAuth Integrations, etc.

With the introduction of API Keys in this release we’re able to provide a simpler Auth Story for .NET 8 Microservices that’s easy for Admin Users to manage and control which trusted clients and B2B Integrations can access their functionality.

Support for RHEL 9’s hardened cryptography policy

A consequence of RedHat Enterprise Linux 9’s hardened system-wide cryptographic policies is that it’s incompatible with ServiceStack’s current licensing which uses RSA encryption and SHA1 hashing algorithm to validate license keys.

Unfortunately this makes it no longer possible to use License Keys to run unrestricted ServiceStack Apps on default installs of RHEL 9 or any of its variants.

Generate License Key for RHEL 9+

Starting from this release you can regenerate a new License Key with a stronger SHA512 Hash Algorithm that’s compatible with RHEL 9’s default hardened cryptography policy by visiting:

https://account.servicestack.net/regenerate-license

With the new API-keys, which are no longer connected to a user (good idea), how do we secure APIs that are both to be reachable by users and API-keys alike?

[Authenticate]
[ValidateApiKey]
public ResponseDTO Any(RequestDTO request) {
   // ...
}

I want both authenticated users, and authenticated non-users (using API key) to be allowed to call the service.

API Keys can still be associated with a User but they don’t have to be, and users can still create their own API Keys.

APIs can be validated to require an Authenticated user AND an API Key but not either, you’d need 2 separate APIs.

If an API is protected by an API Key don’t force them to have Authentication, even though it’s possible no one else does this.

One more thing:

In SS we have the option of setting attributes on the DTO or on the Service (or on the individual service methods).

When using attributes like [ValidateRequest("IsAuthenticated")] on one DTO, then we can just create another DTO using [ValidateApiKey].

How does it work when we have [Authenticated] on the whole service class? Can this then not be used for ApiKey (new style)? Do we need to move all authentication to the DTOs?

APIs should not require both Authentication and API Keys. If you’re requiring APIs to require an API Key, that’s all that should be needed, requiring Authentication in addition will force end users to authenticate before being able to use their API Key, which isn’t how users expect API Keys to work and defeats the purpose of using API Keys.

If your API is called with an API Key that’s assigned to a User you can resolve the UserId inside your Service implementation with:

var apiKey = Request.GetApiKey();
var userId = apiKey.UserAuthId;

Note: API Keys do not need to be assigned to users, and will return null in this case.

Yes, I understand that ApiKey and regular user Auth can not be combined.

The question is what would be your recommendation to where to actually put the attributes?

Using DTOs it’s no problem, because we then just create separate DTOs for ApiKey stuff. And I guess we have to go this route – i.e. move all authentication/api-key attributes to the DTOs.

However SS also supports setting the auth-attributes on the Service class, which is very nice, then there’s less chance of a mistake (forgetting Auth on a single DTO). In this case, I guess we’d need to create two service classes? One for regular use and one for ApiKey use? Or just move the auth-attributes from the class to the methods (still easier to see than on the DTOs we feel, thus avoid human error forgetting the auth-attributes).

The APIs would be executed under different contexts, one is authenticated the other isn’t.

You could use an extension method that your Authenticated and API Key APIs implementations can use to ensure either was used, e.g:

public static class MyExtensions
{
    public static string GetRequiredUserId(this IRequest? req) =>
        req.GetApiKey()?.UserAuthId ??
        req.GetClaimsPrincipal().GetUserId() 
        ?? throw HttpError.Unauthorized("API Key or Authentication required");
}

var userId = Request.GetRequiredUserId();

You could also implement OnBeforeExecute() on your Service class (or base class) to verify all Requests in that Service was called with an API Key, e.g:

public class MyServices : Service
{
    public override void OnBeforeExecute(object requestDto)
    {
        var apiKey = Request.GetApiKey();
        if (apiKey == null)
            throw HttpError.Unauthorized("API Key required");
    }
    //...
}