Hi Mythz,
I’m working on a SPA with a ServiceStack API backend. The SPA uses session authentication for users, but I also want to provide API access as well. I’ve been reading the docs about the new ApiKeysFeature in v8.3+ and want to make sure I understand the recommended pattern correctly.
Current Situation:
- The new
ApiKeysFeaturerequires using[ValidateApiKey]attribute - User authentication requires
[ValidateIsAuthenticated]attribute - These attributes are mutually exclusive - an endpoint can’t accept both
Understanding the Pattern:
Based on the documentation, it seems I need completely separate endpoints, so where previously it would be one GetOrders endpoint, it would now look something like below:
// Base request DTO with shared properties
public abstract class GetOrdersBase : IReturn<List<Order>>
{
public DateTime? FromDate { get; set; }
public string? CustomerId { get; set; }
public string? Status { get; set; }
}
// For authenticated users (SPA calls this)
[ValidateIsAuthenticated]
[Route("/orders", "GET")]
public class GetOrders : GetOrdersBase { }
// For API key access (external partners)
[ValidateApiKey("orders:read")] // with scope
[Route("/api/orders", "GET")]
public class GetOrdersViaApiKey : GetOrdersBase { }
// Service implementation
public class OrderService : Service
{
private List<Order> GetOrdersImpl(GetOrdersBase request, string userId)
{
// Shared business logic
var q = Db.From<Order>();
q.Where(x => x.UserId == userId);
if (request.FromDate.HasValue)
q.Where(x => x.CreatedDate >= request.FromDate);
if (!string.IsNullOrEmpty(request.CustomerId))
q.Where(x => x.CustomerId == request.CustomerId);
return Db.Select(q);
}
public List<Order> Get(GetOrders request)
{
// Get userId from authenticated session
var userId = GetSession().UserAuthId;
return GetOrdersImpl(request, userId);
}
public List<Order> Get(GetOrdersViaApiKey request)
{
// Get userId from API key (if associated with user)
var apiKey = Request.GetApiKey();
if (string.IsNullOrEmpty(apiKey?.UserId))
throw new UnauthorizedAccessException("API Key must be associated with a user");
return GetOrdersImpl(request, apiKey.UserId);
}
}
This seems to add a lot of boilerplate:
- Base DTOs for every endpoint that needs dual auth
- Duplicate concrete DTOs with no additional properties
- Duplicate service methods that just delegate to shared implementation
- Different routes for what is logically the same operation
Am I understanding correctly that this is the intended pattern?
My specific requirement is that I need to avoid authentication requests for API calls because latency is critical for my use case - these APIs are being called by AI tools and I’m fighting to keep latency as low as possible. I need to be able to make machine-to-machine requests without an initial auth request that will add latency to the tool request.
Should I be adding code like my example for all my endpoints where I need API key auth (which is most endpoints), or should I be handling this differently?
Thanks for any guidance!