简体   繁体   中英

Minimum API Key ServiceStack authentication + authorization

I would like to use API key to access secured ServiceStack web service simply as possible:

  • I do not want to be able to register an user
  • I do not need user permissions or roles
  • Custom API key permissions would be a plus:
    • Be able to limit some service to a specific API key.
  • API keys will be managed directly from the database
  • What are the classes or methods I need to override? There are many extension points but I do not know what to keep and what to rewrite:
    • OrmLiteAuthRepository (base?)
    • ApiKeyAuthProvider
    • AuthUserSession

I am able to call a service with Bearer token (API key). It returns 200 Forbidden.

ApiKeyAuthProvider.AuthenticateAsync():

// authRepo is ServiceStack.Auth.OrmLiteAuthRepositoryMultitenancy
var userAuth = await authRepo.GetUserAuthAsync(apiKey.UserAuthId, token).ConfigAwait();

userAuth is NULL and this will throw this exception:

throw HttpError.Unauthorized(ErrorMessages.UserForApiKeyDoesNotExist.Localize(authService.Request));

I store my API keys at the 'ApiKey' table in SQL database:

public override void Configure(Container container) { string connectionString = GetConnectionStringByName("Main"); // Create and register an OrmLite DB Factory configured to use Live DB by default var dbFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider); container.Register(dbFactory);

// Tell ServiceStack you want to persist User Auth Info in SQL Server
container.Register<IAuthRepository>(c => new OrmLiteAuthRepository(dbFactory) { UseDistinctRoleTables = true });

// It’s safe to always call this in your AppHost as it’s just ignored if you already have the tables created
container.Resolve<IAuthRepository>().InitSchema();

Plugins.Add(new AuthFeature(
    () => new AuthUserSession(),
    new IAuthProvider[]
    {
        new ApiKeyAuthProvider(AppSettings) {RequireSecureConnection = false}
    }));

}

The API Key AuthProvider may not suit your use-case as it's designed to generate API Keys for registered users to provide an alternative way for them to call protected APIs.

To be able to model this using ServiceStack's built-in Auth API Key Auth Providers I would still have a registered AuthProvider and users representing the client that would use the API Keys.

But instead of providing User registration functionality, add them into the database manually then Generating API Keys for Existing Users .

You'll need to configure your preferred RDBMS to store the API Keys and Users in:

[assembly: HostingStartup(typeof(MyApp.ConfigureDb))]
public class ConfigureDb : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices((context, services) => 
            services.AddSingleton<IDbConnectionFactory>(
                new OrmLiteConnectionFactory(                  
                    context.Configuration.GetConnectionString("DefaultConnection"),
                    SqliteDialect.Provider)));
}

Configure ServiceStack's Auth Feature configured with the API Key AuthProvider :

[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) {
                       RequireSecureConnection = false,
                       SessionCacheDuration = TimeSpan.FromMinutes(10),
                    }
                }));
        });
}

Then configure an RDBMS OrmLiteAuthRepository pre-populated with the clients you want to allow access to, then generate any missing API Keys for them on startup:

[assembly: HostingStartup(typeof(MyApp.ConfigureAuthRepository))]
public class ConfigureAuthRepository : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services => services.AddSingleton<IAuthRepository>(c =>
            new OrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>())))
        .ConfigureAppHost(appHost => {
            var authRepo = appHost.Resolve<IAuthRepository>();
            authRepo.InitSchema();
            CreateUser(authRepo, "admin@email.com", "Admin User", "p@55wOrd", 
                roles: new[] { RoleNames.Admin });
            CreateUser(authRepo, "admin.client@email.com", "Client Admin", "p@55wOrd", 
                roles: new[] { "ClientAdmin", "Client" });
            CreateUser(authRepo, "client@email.com", "Client User", "p@55wOrd", 
                roles: new[] { "Client" });
        }, 
        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);

            // Use custom UserAuth if configured
            var userIdsMissingKeys = db.Column<string>(db.From<UserAuth>()
                .Where(x => userWithKeysIds.Count == 0 || !userWithKeysIds.Contains(x.Id))
                .Select(x => x.Id));

            var authRepo = (IManageApiKeys)appHost.TryResolve<IAuthRepository>();
            foreach (var userId in userIdsMissingKeys)
            {
                var apiKeys = authProvider.GenerateNewApiKeys(userId);
                authRepo.StoreAll(apiKeys);
            }
        });

    // Add initial Users to the configured Auth Repository
    public void CreateUser(IAuthRepository authRepo, string email, string name, string password, string[] roles)
    {
        if (authRepo.GetUserAuthByUserName(email) == null)
        {
            var newAdmin = new AppUser { Email = email, DisplayName = name };
            var user = authRepo.CreateUserAuth(newAdmin, password);
            authRepo.AssignRoles(user, roles);
        }
    }
}

This will let you protect access to different APIs using role-based auth:

[ValidateIsAdmin]
public class AdminOnly { ... }

[ValidateHasRole("ClientAdmin")]
public class ClientAdminOnly { ... }

[ValidateHasRole("Client")]
public class AnyClient { ... }

Note: The Admin is a super user role that can access any protected API

If you don't want all these Auth components for your App you'll have to create your own Custom Auth Provider that implements its own Authentication which doesn't need to use any other components as it has full control over how the request is authenticated.

You can refer to the existing ApiKeyAuthProvider.cs for a guide on how to implement an API Key IAuthWithRequest Auth Provider that validates the BearerToken in its PreAuthenticateAsync() method .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM