The API Key Auth Provider provides an alternative method for allowing external 3rd Parties access to your protected Services without needing to specify a password. API Keys is the preferred approach for many well-known public API providers used in system-to-system scenarios for several reasons:
- Simple - It integrates easily with existing HTTP Auth functionality
- Independent from Password - Limits exposure to the much more sensitive master user passwords that should ideally never be stored in plain-text. Resetting User's Password or password reset strategies wont invalidate existing systems configured to use API Keys
- Entropy - API Keys are typically much more secure than most normal User Passwords. The configurable default has 24 bytes of entropy (Guids have 16 bytes) generated from a secure random number generator that encodes to 32 chars using URL-safe Base64 (Same as Stripe)
- Performance - Thanks to their much greater entropy and independence from user-chosen passwords, API Keys are validated as fast as possible using a datastore Index. This is contrast to validating hashed user passwords which as a goal require usage of slower and more computationally expensive algorithms to try make brute force attacks infeasible
Like most ServiceStack providers the new API Key Auth Provider is simple to use, integrates seamlessly with ServiceStack existing Auth model and includes Typed end-to-end client/server support.
For familiarity and utility we've modeled our implementation around Stripe's API Key functionality whilst sharing many of the benefits of ServiceStack's Auth Providers:
Simple and Integrated​
To register ApiKeyAuthProvider
add it to the AuthFeature
list of Auth Providers:
Plugins.Add(new AuthFeature(...,
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings),
new CredentialsAuthProvider(AppSettings),
//...
}
));
Or for Apps utilizing encapsulated Modular Startup configuration blocks:
[assembly: HostingStartup(typeof(MyApp.ConfigureAuth))]
public class ConfigureAuth : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureAppHost(appHost => {
appHost.Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new ApiKeyAuthProvider(appHost.AppSettings),
new CredentialsAuthProvider(appHost.AppSettings),
//...
}));
});
}
The ApiKeyAuthProvider
works similarly to the other ServiceStack IAuthWithRequest
providers where a
successful API Key initializes the current IRequest
with the user's Authenticated Session. It also adds the
ApiKey POCO Model to the request which can be accessed with:
ApiKey apiKey = req.GetApiKey();
The ApiKey
can be later inspected throughout the request pipeline to determine which API Key, Type and Environment was used.
Interoperable​
Using existing HTTP Functionality makes it simple and interoperable to use with any HTTP Client even command-line clients like curl where API Keys can be specified in the Username of HTTP Basic Auth:
curl https://api.stripe.com/v1/charges -u yDOr26HsxyhpuRB3qbG07qfCmDhqutnA:
Or as a HTTP Bearer Token in the Authorization HTTP Request Header:
curl https://api.stripe.com/v1/charges -H "Authorization: Bearer yDOr26HsxyhpuRB3qbG07qfCmDhqutnA"
Both of these methods are built into most HTTP Clients. Here are a few different ways which you can send them using ServiceStack's .NET Service Clients:
var client = new JsonServiceClient(baseUrl) {
Credentials = new NetworkCredential(apiKey, "")
};
var client = new JsonHttpClient(baseUrl) {
BearerToken = apiKey
};
Or using the HTTP Utils extension methods:
var response = baseUrl.CombineWith("/secured").GetStringFromUrl(
requestFilter: req => req.AddBasicAuth(apiKey, ""));
var response = await "https://example.org/secured".GetJsonFromUrlAsync(
requestFilter: req => req.AddBearerToken(apiKey));
Sending API Key in Request DTOs​
Similar to the IHasSessionId
interface Request DTOs can also implement IHasBearerToken
to send Bearer Tokens, e.g:
public class Secure : IHasBearerToken
{
public string BearerToken { get; set; }
public string Name { get; set; }
}
var response = client.Get(new Secure { BearerToken = apiKey, Name = "World" });
Alternatively you can set the BearerToken
property on the Service Client once where it will automatically populate all Request DTOs
that implement IHasBearerToken
, e.g:
client.BearerToken = jwtToken;
var response = client.Get(new Secure { Name = "World" });
Supported Auth Repositories​
The necessary functionality to support API Keys has been implemented in the following supported Auth Repositories:
OrmLiteAuthRepository
- Supporting most major RDBMSRedisAuthRepository
- Uses Redis back-end data storeDynamoDbAuthRepository
- Uses AWS DynamoDB data storeMongoDbAuthRepository
- Uses MongoDB data storeInMemoryAuthRepository
- Uses InMemory Auth Repository
And requires no additional configuration as it just utilizes the existing registered IAuthRepository
.
Multiple API Key Types and Environments​
You can specify any number of different Key Types for use in multiple environments for each user. Keys are generated upon User Registration where it generates both a live and test key for the secret Key Type by default. To also create both a "secret" and "publishable" API Key, configure it with:
Plugins.Add(new AuthFeature(...,
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings) {
KeyTypes = new[] { "secret", "publishable" },
}
}
));
If preferred, any of the API Key Provider options can instead be specified in
App Settings following the apikey.{PropertyName}
format, e.g:
<add key="apikey.KeyTypes" value="secret,publishable" />
Cached API Key Sessions​
You can reduce the number of I/O Requests and improve the performance of API Key Auth Provider Requests by
specifying a SessionCacheDuration
to temporarily store the Authenticated UserSession against the API Key
which will reduce subsequent API Key requests down to 1 DB call to fetch and validate the API Key + 1 Cache Hit
to restore the User's Session which if you're using the default in-memory Cache will mean it only requires 1 I/O
call for the DB request.
This can be enabled with:
Plugins.Add(new AuthFeature(...,
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings) {
SessionCacheDuration = TimeSpan.FromMinutes(10),
}
}));
Multitenancy​
Thanks to the ServiceStack's trivial support for enabling Multitenancy, the minimal configuration required to register and API Key Auth Provider that persists to a LiveDb SQL Server database and also allows Services called with an Test API Key to query the alternative TestDb database instead, is just:
class AppHost : AppSelfHostBase
{
public AppHost() : base("API Key Multitenancy Example", typeof(AppHost).Assembly) { }
public override void Configure(Container container)
{
//Create and register an OrmLite DB Factory configured to use Live DB by default
var dbFactory = new OrmLiteConnectionFactory(
AppSettings.GetString("LiveDb"), SqlServerDialect.Provider);
container.Register<IDbConnectionFactory>(dbFactory);
// Register a "TestDb" Named Connection
dbFactory.RegisterConnection("TestDb",
AppSettings.GetString("TestDb"), SqlServerDialect.Provider);
//Tell ServiceStack you want to persist User Auth Info in SQL Server
container.Register<IAuthRepository>(c => new OrmLiteAuthRepository(dbFactory));
//Register the AuthFeature with the API Key Auth Provider
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings)
});
}
public override IDbConnection GetDbConnection(IRequest req = null)
{
//If an API Test Key was used return DB connection to TestDb instead:
return req.GetApiKey()?.Environment == "test"
? TryResolve<IDbConnectionFactory>().OpenDbConnection("TestDb")
: base.GetDbConnection(req);
}
}
Now whenever a Test API Key was used to call an Authenticated Service, all base.Db
Queries or AutoQuery Services will query TestDb instead.
API Key Defaults​
The API Key Auth Provider has several options to customize its behavior with all but delegate Filters being able to be specified in AppSettings as well:
new ApiKeyAuthProvider
{
// Whether to only permit access via API Key from a secure connection. (default true)
public bool RequireSecureConnection { get; set; }
// Generate different keys for different environments. (default live,test)
public string[] Environments { get; set; }
// Different types of Keys each user can have. (default secret)
public string[] KeyTypes { get; set; }
// How much entropy should the generated keys have. (default 24)
public int KeySizeBytes { get; set; }
/// Whether to automatically expire keys. (default no expiry)
public TimeSpan? ExpireKeysAfter { get; set; }
// Automatically create ApiKey Table for Auth Repositories which need it. (true)
public bool InitSchema { get; set; }
// Change how API Key is generated
public CreateApiKeyDelegate GenerateApiKey { get; set; }
// Run custom filter after API Key is created
public Action<ApiKey> CreateApiKeyFilter { get; set; }
// Cache the User Session so it can be reused between subsequent API Key Requests
public TimeSpan? SessionCacheDuration { get; set; }
// Whether to allow API Keys in 'apikey' QueryString or FormData (e.g. `?apikey={APIKEY}`)
public bool AllowInHttpParams { get; set; }
}
IManageApiKeys API​
Should you need to, you can access API Keys from the Auth Repository directly through the following interface:
public interface IManageApiKeys
{
void InitApiKeySchema();
bool ApiKeyExists(string apiKey);
ApiKey GetApiKey(string apiKey);
List<ApiKey> GetUserApiKeys(string userId);
void StoreAll(IEnumerable<ApiKey> apiKeys);
}
INFO
This interface also defines what's required in order to implement API Keys support on a Custom AuthRepository
For Auth Repositories which implement it, you can access the interface by resolving IAuthRepository
from the IOC and casting it to the above interface, e.g:
var apiRepo = (IManageApiKeys)HostContext.TryResolve<IAuthRepository>();
var apiKeys = apiRepo.GetUserApiKeys(session.UserAuthId);
Built-in API Key Services​
To give end-users access to their keys the API Key Auth Provider enables 2 Services: the GetApiKeys
Service to return all valid User API Keys for the specified environment:
//GET /apikeys/live
var response = client.Get(new GetApiKeys { Environment = "live" });
response.Results.PrintDump(); //User's "live" API Keys
And the RegenerateApiKeys
Service to invalidate all current API Keys and generate new ones for the specified environment:
//POST /apikeys/regenerate/live
var response = client.Post(new RegenerateApiKeys { Environment = "live" });
response.Results.PrintDump(); //User's new "live" API Keys
You can modify which built-in Services you want registered, or modify the custom routes to where you want them to be available by modifying the ServiceRoutes
collection. E.g. you can prevent it from registering any Services by setting ServiceRoutes
to an empty collection:
new ApiKeyAuthProvider { ServiceRoutes = new Dictionary<Type, string[]>() }
Generating API Keys for Existing Users​
Whilst the API Key Auth Provider will automatically generate API Keys for new users, if you also want to add API Keys for existing users you'll need to use the ApiKeyAuthProvider
to generate new keys for all users that don't have keys.
Here's a script you can use when using an OrmLiteAuthRepository
to generate API Keys for all users with missing API Keys on startup:
public class ConfigureAuth : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureAppHost(appHost =>
{
appHost.Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new ApiKeyAuthProvider(appHost.AppSettings)
}));
}, afterAppHostInit: appHost => {
var authProvider = (ApiKeyAuthProvider)
AuthenticateService.GetAuthProvider(ApiKeyAuthProvider.Name);
using var db = appHost.TryResolve<IDbConnectionFactory>().Open();
var userWithKeysIds = db.Column<string>(db.From<ApiKey>()
.SelectDistinct(x => x.UserAuthId)).Map(int.Parse);
var userIdsMissingKeys = db.Column<string>(db.From<UserAuth>() // Use custom UserAuth if configured
.Where(x => userWithKeysIds.Count == 0 || !userWithKeysIds.Contains(x.Id))
.Select(x => x.Id));
foreach (var userId in userIdsMissingKeys)
{
var apiKeys = authProvider.GenerateNewApiKeys(userId);
authRepo.StoreAll(apiKeys);
}
});
}
INFO
If using another Auth Repository backend this script will need to be modified to fetch the userIds for all users missing API Keys
.NET Framework Example​
Older platforms can register Startup initialization logic using the HostContext.ConfigureAppHost()
singleton:
HostContext.ConfigureAppHost(afterAppHostInit:appHost => ...);
Or adding to AfterInitCallbacks
in their AppHost.Configure()
, e.g:
public override void Configure(Container container)
{
AfterInitCallbacks.Add(appHost => ...);
}