I really couldn’t find information about migrating the “old” API keys into the new ones so either I am blind or no one else is using this
In the docs, there is an article about using the Identity as an auth provider and the new re-designed API Keys feature, which I believe is now a recommended approach.
We have migrated the users to the Identity, following the tutorial in the docs, but there is no information about migrating the API keys from old ApiKeyAuthProvider to the new ApiKeysFeature.
What would be the approach?
The potential issue is, we have some API clients that rely on the ApiKeyAuthProvider so we probably need some graceful transition, where initially we support both - the auth provider and the ApiKeyFeature, so the clients have time to migrate, eventually dropping the support for ApiKeyAuthProvider.
You wouldn’t be able to support both at the same time as the ApiKeysAuthProvider is only for ServiceStack Auth whilst the new API Keys Feature is only for ASP .NET Identity Auth.
For migration as the IManageApiKeys interface doesn’t provide any way to retrieve all API Keys you’ll need to retrieve them from the underlying data store, e.g if you’re storing them in your RDBMS with OrmLiteAuthRepository you’ll need to use OrmLite to retrieve them all then create new API Keys for each API Key.
You can retain the same API Key by populating the Key property when creating the new API Key, e.g:
if (feature.ApiKeyCount(db) == 0)
{
var oldApiKeys = db.Select<ServiceStack.Auth.ApiKey>();
foreach (var oldKey in oldApiKeys)
{
var user = db.SingleById<ServiceStack.Auth.UserAuth>(oldKey.UserAuthId);
var apiKey = feature.Insert(db,
new() {
Key = oldKey.Id,
Name = oldKey.KeyType,
UserId = oldKey.UserAuthId,
UserName = user.UserName,
ExpiryDate = oldKey.ExpiryDate,
Environment = oldKey.Environment,
RefId = oldKey.RefId,
RefIdStr = oldKey.RefIdStr,
Meta = oldKey.Meta,
});
}
}
Thanks @mythz for a prompt and to-the-point reply, as always!
I found a bit time to experiment with this and came up with this:
var identityAuth = IdentityAuth.For<AppUser>(options =>
{
// override the default IdentityAuthProvider
options.AuthApplication = new IdentityApplicationAuthProviderExt<AppUser, string>();
options.ApplicationAuth();
});
var authFeature = new AuthFeature((services, authFeature) =>
{
identityAuth(services, authFeature);
authFeature.RegisterAuthProvider(new ApiSessionAuthProvider());
});
That way, I can have my custom/legacy api authentication method for some time so the clients can migrate.
The ApiSessionAuthProvider will then listen on the auth/api url and consumes the ServiceStack.Authenticate DTO, but will implement the new identity authentication.
It might look a bit dirty, but it’s only temporary. I quickly tested it and it looks like, it’s working.
Do you think, it’s acceptable? Or this may lead to some other issues I am not aware of?
Can’t say I’ve seen anything like it, and didn’t even occur to me it would be possible, but if it works great. It’s not a use-case it was designed for so there may be issues, but all that’s important is that it works until migration is completed.
What do clients need to migrate? Aren’t they just going to send the same API Key they had before?
The authentication method. We made some custom solution for our on-premise product that usually runs in unencrypted, local networks. Because of that, we’ve decided to, at least, encrypt the API keys during authentication and then use session ID in the HTTP header. So it’s similar to credentials auth provider, but for API keys.
Here is an example of the client authentication. It’s a 2-step process, where the client initially retrieves a salt from the server to be used for the key encryption, then authenticates with encrypted key and username:
So yea - given that: the clients expect the auth/api route to be available and they usually use the ServiceStack.Client library and the ServiceStack.Authenticate class.
What will be the new recommended authentication method is still open to a discussion for us. We really don’t want to send the API keys over unencrypted HTTP in the headers…
The obvious solution would be to use SSL for all https communication.
This doesn’t look particularly safe, other than the race conditions for being able to intercept the incoming Login response with the salt and the outgoing traffic with the encrypted API Token which they can spoof, you’re still sending the session id over unencrypted HTTP, so they’re still going to be able to impersonate and make Authenticated requests either way. An API Key is more like a session Id than a password, it’s a unique generated token with more entropy than a Session Id that will never be reused in other systems like a password might.
This solution looks like security theater, added complexity and latency but it’s not clear which actors this prevents against, it’s still going to vulnerable whether you send an unencrypted API Key or Session Id. If you have to run over HTTP I would’ve gone with an IP filtering solution that maps users with allowed IPs, that would at least limit access to approved PCs/IPs.
If you’re going to redesign this I would quickly abandon this solution and use https everywhere. If you must support plain text also run your app on multiple https + http endpoints or use a SSL terminating proxy.
I completely agree. Ideally, we would assume every customer has an SSL certificate installed and that all traffic is sent over HTTPS. However, in the CNC industry, network technology adoption is significantly behind. While we strongly recommend using SSL and HTTPS, we cannot enforce it.
Some of our customers are very small—often operating just one CNC machine without a proper network infrastructure. Our product, designed for this audience, is a simple web server installed on a Windows PC that collects data from the machine, stores it in an SQL database, and provides a web interface for statistics along with a REST API for further data processing.
Regarding what you’ve called “security theater” (a term I also agree with), there are three key reasons for our approach:
We provide pre-defined vendor API keys to trusted partners and CNC machines that send data to the server. These keys need to be protected from exposure over plain-text HTTP.
We need to establish a session for push notifications, which we handle using SignalR. We initially attempted Server-Sent Events but encountered issues that led us to switch.
The SessionId is more temporary than an API key or password, making it the least harmful option in case of unintended exposure.
I’m also aware of the Encrypted Messaging feature you’ve implemented—it’s excellent, and we use it for CNC machines sending data. However, we also need to support languages beyond C#.
I fully agree that HTTPS everywhere would be the ideal solution. Unfortunately, requiring it would mean abandoning a portion of our customer base. These smaller customers understand the risks and take responsibility for securing their LAN. Given their constraints, we have to work within these limitations and find a suitable authentication mechanism for identity-based API keys.