Base solution for your next web application
Open Closed

Request for Feedback on API Key Implementation #12524


User avatar
0
kylem created

Dear Support Team,

We understand that API Key support has been discussed previously and is not currently a priority for ASP.NET Zero. However, due to client demand, we have developed a potential solution to add API Key functionality and would appreciate your feedback on its security and performance implications.

Background

We reviewed related discussions, including:

  • GitHub issue: https://github.com/aspnetzero/aspnet-zero-core/issues/1728
  • Support question: https://support.aspnetzero.com/QA/Questions/11251/Create-static-access-token-valid-for-365-days
  • OpenIddict documentation

Based on this, we implemented a potential solution that aligns with ASP.NET Zero’s existing authentication and authorization mechanisms.

Our Proposed Solution

  • API Tokens as JWTs: API Tokens are implemented as JWTs, leveraging the same mechanism as the /api/TokenAuth/Authenticate endpoint for access and refresh tokens.
  • Customizable Expiry: Users can specify an expiry longer than 24 hours during token creation.
  • Permission Scoping: Tokens can be assigned specific permissions, restricted to those already granted to the user. If a token’s permissions exceed the user’s at the time of use, access is denied to minimize security risks. Permissions are encoded as additional claims.
  • Authorization Header: API Tokens use the ApiKey Authorization header to distinguish them from Bearer tokens.
  • Token Tracking: Tokens are tracked in the AbpUserTokens table (like access/refresh tokens) and a new ApiTokens table for additional metadata. Tokens themselves are never stored in the database.
  • Token Validation: Tokens are validated identically as access and refresh token, utilizing TokenValidity claim
  • Token Revocation: Revoking a token removes its records from cache and from both AbpUserTokens and ApiTokens tables.
  • Permission Validation: We extended PermissionChecker.cs to validate API Key permissions, as shown below:
public override async Task<bool> IsGrantedAsync(long userId, string permissionName)
{
    Logger.Info($"PermissionChecker.IsGrantedAsync called - UserId: {userId}, Permission: {permissionName}");
    
    var httpContext = _httpContextAccessor.HttpContext;
    if (httpContext?.User?.Identity?.IsAuthenticated == true)
    {
        var tokenTypeClaim = httpContext.User.FindFirst("TokenType");
        Logger.Info($"PermissionChecker - TokenType claim: {tokenTypeClaim?.Value}");
        
        if (tokenTypeClaim?.Value == "ApiKey")
        {
            Logger.Info("PermissionChecker - API Key authentication detected, checking API key permissions");
            
            // Check if the user has the permission
            var userHasPermission = await base.IsGrantedAsync(userId, permissionName);
            Logger.Info($"PermissionChecker - User permission check result: {userHasPermission} for permission: {permissionName}");
            
            if (!userHasPermission)
            {
                Logger.Info($"PermissionChecker - User {userId} does not have permission {permissionName}, denying API key access");
                return false;
            }
            
            // Check API Key permissions
            var apiKeyPermissionsClaim = httpContext.User.FindFirst("ApiKeyPermissions");
            if (apiKeyPermissionsClaim != null && !string.IsNullOrEmpty(apiKeyPermissionsClaim.Value))
            {
                try
                {
                    var apiKeyPermissions = JsonConvert.DeserializeObject<List<string>>(apiKeyPermissionsClaim.Value);
                    
                    if (apiKeyPermissions.Contains(DenvrDashboardConsts.ApiKey.AllUserPermissions))
                    {
                        Logger.Info($"PermissionChecker - API key has 'AllUserPermissions', granting access to permission: {permissionName}");
                        return true;
                    }
                    
                    bool apiKeyHasPermission = apiKeyPermissions.Contains(permissionName);
                    Logger.Info($"PermissionChecker - API key permission check result: {apiKeyHasPermission} for permission: {permissionName}");
                    return apiKeyHasPermission;
                }
                catch (JsonException ex)
                {
                    Logger.Error($"PermissionChecker - Error deserializing API key permissions: {ex.Message}");
                    return false;
                }
            }
            else
            {
                Logger.Warn("PermissionChecker - No ApiKeyPermissions claim found for API key authentication");
                return false;
            }
        }
    }

    Logger.Info("PermissionChecker - Using default permission checking logic");
    return await base.IsGrantedAsync(userId, permissionName);
}
  • Middleware: We added ApiAuthenticationMiddleware.cs after JwtTokenMiddleware in Startup.cs to validate API Keys and extract claims. It only processes requests with the ApiKey header, minimizing performance impact:
app.UseAuthentication();
app.UseJwtTokenMiddleware();
app.UseMiddleware<ApiKeyAuthenticationMiddleware>();

Questions for Support

We believe this solution aligns with ASP.NET Zero’s architecture and addresses client needs for API Key support. We’d appreciate your feedback on:

  • Potential security risks, particularly around token revocation or JWT payload handling.
  • Performance implications of the middleware and permission validation logic.
  • Any suggestions for improving compatibility with ASP.NET Zero’s ecosystem.

If needed, we can share a GitHub repository or detailed documentation for further review. Please let us know how you’d like to proceed (e.g., via a pull request or further discussion).

Best regards,
Peja

Markdown is supported
Copy & paste or drag & drop images (max 30 MB per image)

2 Answer(s)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hello @kylem

    Thank you for sharing your detailed solution and explanation. While API Key support is not currently a priority in ASP.NET Zero’s roadmap, you’ve developed a well thought out approach to meet your needs.

    Your implementation appears to align well with our existing architecture, particularly in the following aspects

    JWT based token mechanism reuse Reusing the /api/TokenAuth/Authenticate process is a positive choice in terms of compatibility with the existing security structure.

    Permission scoping Ensuring that an API Key cannot exceed the user’s existing permissions is critical and the right decision from a security perspective.

    Token revocation and cache clearing Removing the token from both the database and the cache helps minimize the risk of unauthorized usage.

    Middleware order Placing your ApiKeyAuthenticationMiddleware after JwtTokenMiddleware helps reduce unnecessary processing for performance reasons.

    Our feedback and recommendations:

    Token Revocation Revocation is always challenging with JWT since tokens are stateless. You’ve addressed this using the AbpUserTokens + ApiTokens tables and cache, but you should pay close attention to cache lifetime and ensuring expired tokens are not revalidated. We recommend validating the TokenValidity claim both in middleware and at the PermissionChecker level.

    Token Claims Validation Since you store ApiKeyPermissions as JSON in claims, parsing errors could affect performance and security. Therefore:

    • Add JSON schema validation when setting the claim.
    • Perform deserialization outside of a try catch block by first validating the string.

    Performance Impact Since the additional middleware only runs when the ApiKey header is present, its impact on overall traffic should be minimal. However, during permission checks, you validate both the user’s and the API Key’s permissions. For scenarios with a large number of permissions, we recommend using a HashSet<string> to optimize lookup performance.

    Standards Alignment To ensure easier integration if ASP.NET Zero adds native API Key support in the future.

    • Keep the middleware modular.
    • Manage claim names through constants.
    • Consider adding an option to support non user bound (system level) API Keys.

    If you have any other questions or would like more information about the status of the feature you’ve added, please don’t hesitate to contact us.

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    kylem created

    Dear Support Team,

    Thank you for your quick response. We greatly appreciate your feedback and the recommended improvements, as well as the points to pay attention to. We will review these suggestions carefully and prioritize their implementation to ensure our solution remains secure and efficient.

    Regarding your suggestion for non-user-bound (system-level) API keys, we currently recommend that our clients create a system user as a regular user with two-factor authentication disabled, as this aligns with our existing architecture and simplifies integration. We plan to maintain this approach in the short term but will monitor your release notes for updates on native API Key support and how this functionality is addressed in future ASP.NET Zero releases.

    Feel free to close the ticket and thanks again!

    Best regards, Peja

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)