Indiko.Blocks.Security.Authentication.Abstractions 2.1.1

dotnet add package Indiko.Blocks.Security.Authentication.Abstractions --version 2.1.1
                    
NuGet\Install-Package Indiko.Blocks.Security.Authentication.Abstractions -Version 2.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Indiko.Blocks.Security.Authentication.Abstractions" Version="2.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Indiko.Blocks.Security.Authentication.Abstractions" Version="2.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Indiko.Blocks.Security.Authentication.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Indiko.Blocks.Security.Authentication.Abstractions --version 2.1.1
                    
#r "nuget: Indiko.Blocks.Security.Authentication.Abstractions, 2.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Indiko.Blocks.Security.Authentication.Abstractions@2.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Indiko.Blocks.Security.Authentication.Abstractions&version=2.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Indiko.Blocks.Security.Authentication.Abstractions&version=2.1.1
                    
Install as a Cake Tool

Indiko.Blocks.Security.Authentication.Abstractions

Core authentication abstractions for implementing secure authentication and token management in the Indiko framework.

Overview

This package provides fundamental contracts and models for implementing authentication systems, including JWT tokens, identity management, and token providers.

Features

  • ITokenProvider: Interface for token generation and management
  • TokenResponse: Standardized token response model
  • IdentityUser: User identity representation
  • IdentityClient: Client/service identity representation
  • AuthenticationOptions: Configuration model for authentication
  • ClaimsIdentity Extensions: Helper methods for claims manipulation
  • Token Validation: Exception handling for token validation

Installation

dotnet add package Indiko.Blocks.Security.Authentication.Abstractions

Key Interfaces

ITokenProvider

Interface for generating authentication tokens.

public interface ITokenProvider
{
    Task<TokenResponse> GetToken(IdentityUser user, CancellationToken cancellationToken = default);
    
    Task<TokenResponse> GetToken(IdentityUser user, Dictionary<string, string> customClaims, 
        CancellationToken cancellationToken = default);
    
    Task<TokenResponse> GetToken(IdentityClient client, CancellationToken cancellationToken = default);
}

Models

TokenResponse

Represents authentication token response.

public sealed class TokenResponse
{
    public Dictionary<string, string> Properties { get; init; }
    public required string AccessToken { get; init; }
    public required string IdentityToken { get; init; }
    public required string RefreshToken { get; init; }
}

IdentityUser

Represents an authenticated user.

public class IdentityUser
{
    public string UserId { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public string[] Roles { get; set; }
    public Dictionary<string, string> Claims { get; set; }
}

IdentityClient

Represents a client application or service.

public class IdentityClient
{
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public string[] Scopes { get; set; }
}

Usage Examples

Implementing Token Provider

public class JwtTokenProvider : ITokenProvider
{
    private readonly AuthenticationOptions _options;
    private readonly ILogger<JwtTokenProvider> _logger;

    public JwtTokenProvider(
        IOptions<AuthenticationOptions> options,
        ILogger<JwtTokenProvider> logger)
    {
        _options = options.Value;
        _logger = logger;
    }

    public async Task<TokenResponse> GetToken(
        IdentityUser user, 
        CancellationToken cancellationToken = default)
    {
        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, user.UserId),
            new(ClaimTypes.Name, user.Username),
            new(ClaimTypes.Email, user.Email)
        };

        foreach (var role in user.Roles)
        {
            claims.Add(new Claim(ClaimTypes.Role, role));
        }

        var token = GenerateJwtToken(claims);
        var refreshToken = GenerateRefreshToken();

        return new TokenResponse
        {
            AccessToken = token,
            IdentityToken = token,
            RefreshToken = refreshToken,
            Properties = new Dictionary<string, string>
            {
                { "expires_in", "3600" },
                { "token_type", "Bearer" }
            }
        };
    }

    public async Task<TokenResponse> GetToken(
        IdentityUser user, 
        Dictionary<string, string> customClaims,
        CancellationToken cancellationToken = default)
    {
        var tokenResponse = await GetToken(user, cancellationToken);
        
        // Add custom claims to properties
        foreach (var claim in customClaims)
        {
            tokenResponse.Properties[claim.Key] = claim.Value;
        }
        
        return tokenResponse;
    }

    public async Task<TokenResponse> GetToken(
        IdentityClient client, 
        CancellationToken cancellationToken = default)
    {
        // Client credentials flow
        var claims = new List<Claim>
        {
            new("client_id", client.ClientId),
            new("scope", string.Join(" ", client.Scopes))
        };

        var token = GenerateJwtToken(claims);

        return new TokenResponse
        {
            AccessToken = token,
            IdentityToken = token,
            RefreshToken = string.Empty,
            Properties = new Dictionary<string, string>
            {
                { "expires_in", "3600" },
                { "token_type", "Bearer" }
            }
        };
    }
}

Authentication Service

public class AuthenticationService
{
    private readonly ITokenProvider _tokenProvider;
    private readonly IUserRepository _userRepository;

    public AuthenticationService(
        ITokenProvider tokenProvider,
        IUserRepository userRepository)
    {
        _tokenProvider = tokenProvider;
        _userRepository = userRepository;
    }

    public async Task<TokenResponse> AuthenticateAsync(
        string username, 
        string password)
    {
        // Validate credentials
        var user = await _userRepository.ValidateCredentialsAsync(username, password);
        if (user == null)
            throw new UnauthorizedAccessException("Invalid credentials");

        // Create identity user
        var identityUser = new IdentityUser
        {
            UserId = user.Id.ToString(),
            Username = user.Username,
            Email = user.Email,
            Roles = user.Roles.ToArray()
        };

        // Generate tokens
        return await _tokenProvider.GetToken(identityUser);
    }

    public async Task<TokenResponse> AuthenticateWithCustomClaimsAsync(
        string username,
        string password,
        Dictionary<string, string> customClaims)
    {
        var user = await _userRepository.ValidateCredentialsAsync(username, password);
        if (user == null)
            throw new UnauthorizedAccessException("Invalid credentials");

        var identityUser = new IdentityUser
        {
            UserId = user.Id.ToString(),
            Username = user.Username,
            Email = user.Email,
            Roles = user.Roles.ToArray()
        };

        return await _tokenProvider.GetToken(identityUser, customClaims);
    }
}

ClaimsIdentity Extensions

Helper methods for working with claims.

using Indiko.Blocks.Security.Authentication.Abstractions.Extensions;

public class UserService
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public string GetCurrentUserId()
    {
        var identity = _httpContextAccessor.HttpContext?.User?.Identity as ClaimsIdentity;
        return identity?.GetUserId();
    }

    public string GetCurrentUserEmail()
    {
        var identity = _httpContextAccessor.HttpContext?.User?.Identity as ClaimsIdentity;
        return identity?.GetUserEmail();
    }

    public string[] GetCurrentUserRoles()
    {
        var identity = _httpContextAccessor.HttpContext?.User?.Identity as ClaimsIdentity;
        return identity?.GetRoles();
    }

    public bool HasRole(string role)
    {
        var identity = _httpContextAccessor.HttpContext?.User?.Identity as ClaimsIdentity;
        return identity?.HasRole(role) ?? false;
    }
}

Authentication Options

Configuration model for authentication settings.

public class AuthenticationOptions
{
    public string Authority { get; set; }
    public string Audience { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public string[] Scopes { get; set; }
    public string SigningKey { get; set; }
    public string Issuer { get; set; }
    public int TokenExpirationMinutes { get; set; } = 60;
    public int RefreshTokenExpirationDays { get; set; } = 7;
}

Configuration Example

{
  "AuthenticationOptions": {
    "Authority": "https://identity.example.com",
    "Audience": "api.example.com",
    "ClientId": "my-api",
    "ClientSecret": "secret",
    "Scopes": ["openid", "profile", "email", "api"],
    "SigningKey": "your-256-bit-secret-key-here",
    "Issuer": "https://api.example.com",
    "TokenExpirationMinutes": 60,
    "RefreshTokenExpirationDays": 7
  }
}

Exception Handling

TokenValidationException

Custom exception for token validation errors.

public class TokenValidationException : Exception
{
    public TokenValidationException(string message) : base(message)
    {
    }

    public TokenValidationException(string message, Exception innerException) 
        : base(message, innerException)
    {
    }
}

Usage

public class TokenValidator
{
    public void ValidateToken(string token)
    {
        try
        {
            // Validate token logic
            if (string.IsNullOrEmpty(token))
                throw new TokenValidationException("Token is null or empty");

            // Additional validation
        }
        catch (Exception ex)
        {
            throw new TokenValidationException("Token validation failed", ex);
        }
    }
}

Authentication Flows

User Authentication Flow

public class LoginController : ControllerBase
{
    private readonly ITokenProvider _tokenProvider;
    private readonly IUserRepository _userRepository;

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        // 1. Validate credentials
        var user = await _userRepository.ValidateCredentialsAsync(
            request.Username, 
            request.Password);

        if (user == null)
            return Unauthorized(new { error = "Invalid credentials" });

        // 2. Create identity user
        var identityUser = new IdentityUser
        {
            UserId = user.Id.ToString(),
            Username = user.Username,
            Email = user.Email,
            Roles = user.Roles.ToArray(),
            Claims = new Dictionary<string, string>
            {
                { "tenant_id", user.TenantId.ToString() },
                { "subscription_tier", user.SubscriptionTier }
            }
        };

        // 3. Generate tokens
        var tokenResponse = await _tokenProvider.GetToken(identityUser);

        // 4. Return tokens
        return Ok(new
        {
            access_token = tokenResponse.AccessToken,
            refresh_token = tokenResponse.RefreshToken,
            expires_in = tokenResponse.Properties["expires_in"],
            token_type = tokenResponse.Properties["token_type"]
        });
    }
}

Client Credentials Flow

public class ClientAuthenticationService
{
    private readonly ITokenProvider _tokenProvider;

    public async Task<TokenResponse> AuthenticateClientAsync(
        string clientId, 
        string clientSecret)
    {
        // Validate client credentials
        if (!IsValidClient(clientId, clientSecret))
            throw new UnauthorizedAccessException("Invalid client credentials");

        var client = new IdentityClient
        {
            ClientId = clientId,
            ClientSecret = clientSecret,
            Scopes = new[] { "api.read", "api.write" }
        };

        return await _tokenProvider.GetToken(client);
    }
}

Token Refresh

public class TokenRefreshService
{
    private readonly ITokenProvider _tokenProvider;
    private readonly IRefreshTokenRepository _refreshTokenRepository;

    public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
    {
        // Validate refresh token
        var storedToken = await _refreshTokenRepository.GetByTokenAsync(refreshToken);
        if (storedToken == null || storedToken.IsExpired)
            throw new TokenValidationException("Invalid or expired refresh token");

        // Get user
        var user = await GetUserByIdAsync(storedToken.UserId);

        var identityUser = new IdentityUser
        {
            UserId = user.Id.ToString(),
            Username = user.Username,
            Email = user.Email,
            Roles = user.Roles.ToArray()
        };

        // Generate new tokens
        var newTokens = await _tokenProvider.GetToken(identityUser);

        // Revoke old refresh token
        await _refreshTokenRepository.RevokeAsync(refreshToken);

        return newTokens;
    }
}

Best Practices

  1. Secure Storage: Never store tokens in plain text
  2. Token Expiration: Use appropriate expiration times (short for access, longer for refresh)
  3. Refresh Tokens: Implement token refresh for better UX
  4. Claims: Include only necessary claims in tokens
  5. Validation: Always validate tokens on protected endpoints
  6. HTTPS: Always use HTTPS in production
  7. Secret Management: Use secure secret management (Azure Key Vault, AWS Secrets Manager)

Target Framework

  • .NET 10

Dependencies

  • Indiko.Blocks.Common.Abstractions

License

See LICENSE file in the repository root.

  • Indiko.Blocks.Security.Authentication.ASPNetCore - ASP.NET Core authentication implementation
  • Indiko.Blocks.Security.AuthenticationProvider.Abstractions - Authentication provider abstractions
  • Indiko.Blocks.Security.AuthenticationProvider.Blazor - Blazor authentication provider
  • Indiko.Hosting.Web - Web API hosting
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Indiko.Blocks.Security.Authentication.Abstractions:

Package Downloads
Indiko.Blocks.Security.Authentication.OpenIdDict

Building Blocks Security Authentication Open Id Dict

Indiko.Blocks.Security.Authentication.ASPNetCore

Building Blocks Security Authentication ASPNet Core

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.1 649 12/2/2025
2.1.0 651 12/2/2025
2.0.0 296 9/17/2025
1.7.23 198 9/8/2025
1.7.22 195 9/8/2025
1.7.21 202 8/14/2025
1.7.20 222 6/23/2025
1.7.19 203 6/3/2025
1.7.18 205 5/29/2025
1.7.17 201 5/26/2025
1.7.15 154 4/12/2025
1.7.14 178 4/11/2025
1.7.13 165 3/29/2025
1.7.12 177 3/28/2025
1.7.11 186 3/28/2025
1.7.10 177 3/28/2025
1.7.9 206 3/28/2025
1.7.8 182 3/28/2025
1.7.5 205 3/17/2025
1.7.4 192 3/16/2025
1.7.3 221 3/16/2025
1.7.2 208 3/16/2025
1.7.1 239 3/11/2025
1.7.0 220 3/11/2025
1.6.8 240 3/11/2025
1.6.7 267 3/4/2025
1.6.6 168 2/26/2025
1.6.5 187 2/20/2025
1.6.4 167 2/20/2025
1.6.3 170 2/5/2025
1.6.2 140 1/24/2025
1.6.1 152 1/24/2025
1.6.0 150 1/16/2025
1.5.2 148 1/16/2025
1.5.1 200 11/3/2024
1.5.0 209 10/26/2024
1.3.2 178 10/24/2024
1.3.0 174 10/10/2024
1.2.5 186 10/9/2024
1.2.4 188 10/8/2024
1.2.1 178 10/3/2024
1.2.0 187 9/29/2024
1.1.1 202 9/23/2024
1.1.0 230 9/18/2024
1.0.33 236 9/15/2024
1.0.28 199 8/28/2024
1.0.27 218 8/24/2024
1.0.26 205 7/7/2024
1.0.25 203 7/6/2024
1.0.24 203 6/25/2024
1.0.23 191 6/1/2024
1.0.22 216 5/14/2024
1.0.21 213 5/14/2024
1.0.20 205 4/8/2024
1.0.19 213 4/3/2024
1.0.18 208 3/23/2024
1.0.17 214 3/19/2024
1.0.16 219 3/19/2024
1.0.15 223 3/11/2024
1.0.14 211 3/10/2024
1.0.13 230 3/6/2024
1.0.12 264 3/1/2024
1.0.11 206 3/1/2024
1.0.10 203 3/1/2024
1.0.9 207 3/1/2024
1.0.8 235 2/19/2024
1.0.7 214 2/17/2024
1.0.6 221 2/17/2024
1.0.5 197 2/17/2024
1.0.4 210 2/7/2024
1.0.3 235 2/6/2024
1.0.1 185 2/6/2024
1.0.0 245 1/9/2024
1.0.0-preview99 225 12/22/2023
1.0.0-preview98 202 12/21/2023
1.0.0-preview97 186 12/21/2023
1.0.0-preview96 173 12/20/2023
1.0.0-preview95 170 12/20/2023
1.0.0-preview94 189 12/18/2023
1.0.0-preview93 194 12/13/2023
1.0.0-preview92 206 12/13/2023
1.0.0-preview91 195 12/12/2023
1.0.0-preview90 178 12/11/2023
1.0.0-preview89 223 12/11/2023
1.0.0-preview88 226 12/6/2023
1.0.0-preview87 196 12/6/2023
1.0.0-preview86 192 12/6/2023
1.0.0-preview85 217 12/6/2023
1.0.0-preview84 202 12/5/2023
1.0.0-preview83 196 12/5/2023
1.0.0-preview82 183 12/5/2023
1.0.0-preview81 211 12/4/2023
1.0.0-preview80 187 12/1/2023
1.0.0-preview77 198 12/1/2023
1.0.0-preview76 190 12/1/2023
1.0.0-preview75 196 12/1/2023
1.0.0-preview74 187 11/26/2023
1.0.0-preview73 244 11/7/2023
1.0.0-preview72 222 11/6/2023
1.0.0-preview71 236 11/3/2023
1.0.0-preview70 189 11/2/2023
1.0.0-preview69 195 11/2/2023
1.0.0-preview68 189 11/2/2023
1.0.0-preview67 205 11/2/2023
1.0.0-preview66 169 11/2/2023
1.0.0-preview65 195 11/2/2023
1.0.0-preview64 230 11/2/2023
1.0.0-preview63 195 11/2/2023
1.0.0-preview62 198 11/1/2023
1.0.0-preview61 184 11/1/2023
1.0.0-preview60 203 11/1/2023
1.0.0-preview59 186 11/1/2023
1.0.0-preview58 225 10/31/2023
1.0.0-preview57 203 10/31/2023
1.0.0-preview56 190 10/31/2023
1.0.0-preview55 222 10/31/2023
1.0.0-preview54 185 10/31/2023
1.0.0-preview53 199 10/31/2023
1.0.0-preview52 210 10/31/2023
1.0.0-preview51 222 10/31/2023
1.0.0-preview50 196 10/31/2023
1.0.0-preview48 203 10/31/2023
1.0.0-preview46 201 10/31/2023
1.0.0-preview45 193 10/31/2023
1.0.0-preview44 218 10/31/2023
1.0.0-preview43 195 10/31/2023
1.0.0-preview42 223 10/30/2023
1.0.0-preview41 216 10/30/2023
1.0.0-preview40 188 10/27/2023
1.0.0-preview39 213 10/27/2023
1.0.0-preview38 183 10/27/2023
1.0.0-preview37 196 10/27/2023
1.0.0-preview36 178 10/27/2023
1.0.0-preview35 205 10/27/2023
1.0.0-preview34 193 10/27/2023
1.0.0-preview33 236 10/26/2023
1.0.0-preview32 221 10/26/2023
1.0.0-preview101 177 1/5/2024