Indiko.Blocks.Security.Authentication.ASPNetCore
2.1.1
dotnet add package Indiko.Blocks.Security.Authentication.ASPNetCore --version 2.1.1
NuGet\Install-Package Indiko.Blocks.Security.Authentication.ASPNetCore -Version 2.1.1
<PackageReference Include="Indiko.Blocks.Security.Authentication.ASPNetCore" Version="2.1.1" />
<PackageVersion Include="Indiko.Blocks.Security.Authentication.ASPNetCore" Version="2.1.1" />
<PackageReference Include="Indiko.Blocks.Security.Authentication.ASPNetCore" />
paket add Indiko.Blocks.Security.Authentication.ASPNetCore --version 2.1.1
#r "nuget: Indiko.Blocks.Security.Authentication.ASPNetCore, 2.1.1"
#:package Indiko.Blocks.Security.Authentication.ASPNetCore@2.1.1
#addin nuget:?package=Indiko.Blocks.Security.Authentication.ASPNetCore&version=2.1.1
#tool nuget:?package=Indiko.Blocks.Security.Authentication.ASPNetCore&version=2.1.1
Indiko.Blocks.Security.Authentication.ASPNetCore
JWT Bearer authentication implementation for ASP.NET Core APIs with token generation and validation.
Overview
This package provides complete JWT (JSON Web Token) authentication for ASP.NET Core applications, including token generation, validation, and SignalR hub support.
Features
- JWT Bearer Authentication: Standard JWT token authentication
- Token Generation: Built-in token provider
- Token Validation: Automatic token validation middleware
- SignalR Support: Query string token support for SignalR hubs
- Configurable Validation: Control issuer, audience, lifetime validation
- Development Mode: Relaxed HTTPS requirements in development
- Comprehensive Logging: Detailed authentication event logging
Installation
dotnet add package Indiko.Blocks.Security.Authentication.ASPNetCore
Configuration
appsettings.json
{
"AspNetCoreAuthenticationOptions": {
"Enabled": true,
"Secret": "your-256-bit-secret-key-minimum-32-characters",
"Issuer": "https://api.example.com",
"Audience": "api.example.com",
"ValidateIssuer": true,
"ValidateAudience": true,
"ValidateLifetime": true,
"ValidateIssuerSigningKey": true,
"TokenExpirationMinutes": 60,
"RefreshTokenExpirationDays": 7,
"SignalRHubPath": "/hubs"
}
}
Quick Start
// Authentication is auto-configured via block system
// Just ensure appsettings.json has the configuration above
Token Generation
Using ITokenProvider
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly ITokenProvider _tokenProvider;
private readonly IUserService _userService;
public AuthController(ITokenProvider tokenProvider, IUserService userService)
{
_tokenProvider = tokenProvider;
_userService = userService;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
// Validate credentials
var user = await _userService.ValidateCredentialsAsync(
request.Username,
request.Password);
if (user == null)
return Unauthorized(new { message = "Invalid credentials" });
// Create identity user
var identityUser = new IdentityUser
{
UserId = user.Id.ToString(),
Username = user.Username,
Email = user.Email,
Roles = new[] { "User", "Admin" }
};
// Generate tokens
var tokenResponse = await _tokenProvider.GetToken(identityUser);
return Ok(new
{
access_token = tokenResponse.AccessToken,
token_type = "Bearer",
expires_in = 3600,
refresh_token = tokenResponse.RefreshToken
});
}
}
With Custom Claims
[HttpPost("login-with-claims")]
public async Task<IActionResult> LoginWithClaims([FromBody] LoginRequest request)
{
var user = await _userService.ValidateCredentialsAsync(request.Username, request.Password);
if (user == null) return Unauthorized();
var identityUser = new IdentityUser
{
UserId = user.Id.ToString(),
Username = user.Username,
Email = user.Email,
Roles = user.Roles.ToArray()
};
var customClaims = new Dictionary<string, string>
{
{ "tenant_id", user.TenantId.ToString() },
{ "subscription_tier", user.SubscriptionTier },
{ "permissions", string.Join(",", user.Permissions) }
};
var tokenResponse = await _tokenProvider.GetToken(identityUser, customClaims);
return Ok(tokenResponse);
}
Protected Endpoints
Require Authentication
[ApiController]
[Route("api/[controller]")]
[Authorize] // Requires valid JWT token
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetUsers()
{
return Ok(users);
}
[HttpGet("me")]
public IActionResult GetCurrentUser()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var username = User.FindFirst(ClaimTypes.Name)?.Value;
var email = User.FindFirst(ClaimTypes.Email)?.Value;
return Ok(new { userId, username, email });
}
}
Role-Based Authorization
[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public IActionResult DeleteUser(Guid id)
{
// Only admins can delete
return NoContent();
}
[Authorize(Roles = "Admin,Moderator")]
[HttpPut("{id}/ban")]
public IActionResult BanUser(Guid id)
{
// Admins or Moderators can ban
return NoContent();
}
Policy-Based Authorization
// In Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("RequirePremium", policy =>
policy.RequireClaim("subscription_tier", "Premium", "Enterprise"));
options.AddPolicy("MinimumAge", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
// In Controller
[Authorize(Policy = "RequirePremium")]
[HttpGet("premium-content")]
public IActionResult GetPremiumContent()
{
return Ok(premiumContent);
}
SignalR Hub Authentication
The block automatically supports SignalR hub authentication via query strings:
// Client-side JavaScript
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/chat", {
accessTokenFactory: () => localStorage.getItem("access_token")
})
.build();
await connection.start();
Secured Hub
[Authorize]
public class ChatHub : Hub
{
public override async Task OnConnectedAsync()
{
var username = Context.User?.Identity?.Name;
Console.WriteLine($"{username} connected");
await base.OnConnectedAsync();
}
public async Task SendMessage(string message)
{
var username = Context.User?.Identity?.Name;
await Clients.All.SendAsync("ReceiveMessage", username, message);
}
}
Token Validation
Tokens are automatically validated on every request to protected endpoints:
- Signature: Verified using the secret key
- Expiration: Checked if
ValidateLifetimeis true - Issuer: Validated if
ValidateIssueris true - Audience: Validated if
ValidateAudienceis true
Manual Validation
public class TokenValidator
{
private readonly AspNetCoreAuthenticationOptions _options;
public bool ValidateToken(string token, out ClaimsPrincipal principal)
{
var tokenHandler = new JwtSecurityTokenHandler();
principal = null;
try
{
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = _options.ValidateIssuer,
ValidateAudience = _options.ValidateAudience,
ValidateLifetime = _options.ValidateLifetime,
ValidIssuer = _options.Issuer,
ValidAudience = _options.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_options.Secret))
};
principal = tokenHandler.ValidateToken(token, validationParameters, out _);
return true;
}
catch
{
return false;
}
}
}
Client Usage
JavaScript/TypeScript
// Login
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'pass' })
});
const { access_token } = await response.json();
localStorage.setItem('access_token', access_token);
// Protected request
const data = await fetch('/api/users', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});
C# HttpClient
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("https://api.example.com/api/users");
Refresh Tokens
[HttpPost("refresh")]
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest request)
{
// Validate refresh token
var storedToken = await _tokenRepository.GetByTokenAsync(request.RefreshToken);
if (storedToken == null || storedToken.IsExpired)
return Unauthorized(new { message = "Invalid refresh token" });
// Get user
var user = await _userService.GetByIdAsync(storedToken.UserId);
var identityUser = new IdentityUser
{
UserId = user.Id.ToString(),
Username = user.Username,
Email = user.Email,
Roles = user.Roles.ToArray()
};
// Generate new tokens
var tokenResponse = await _tokenProvider.GetToken(identityUser);
// Revoke old refresh token
await _tokenRepository.RevokeAsync(request.RefreshToken);
return Ok(tokenResponse);
}
Event Logging
The block logs detailed authentication events:
- OnMessageReceived: Token received from request
- OnTokenValidated: Token successfully validated
- OnAuthenticationFailed: Authentication failure
- OnChallenge: Authentication challenge issued
- OnForbidden: Access denied
Best Practices
- Strong Secrets: Use 256-bit (32+ characters) secret keys
- HTTPS: Always use HTTPS in production
- Short Expiration: Keep access token expiration short (15-60 minutes)
- Refresh Tokens: Implement refresh token flow
- Secure Storage: Don't log or expose secret keys
- Environment-Specific: Different secrets per environment
Target Framework
- .NET 10
Dependencies
Indiko.Blocks.Security.Authentication.AbstractionsMicrosoft.AspNetCore.Authentication.JwtBearerSystem.IdentityModel.Tokens.Jwt
License
See LICENSE file in the repository root.
Related Packages
Indiko.Blocks.Security.Authentication.Abstractions- Authentication abstractionsIndiko.Blocks.Security.AuthenticationProvider.Blazor- Blazor authenticationIndiko.Hosting.Web- Web API hosting
| Product | Versions 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. |
-
net10.0
- Indiko.Blocks.Security.Authentication.Abstractions (>= 2.1.1)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.0)
- Microsoft.EntityFrameworkCore (>= 10.0.0)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 8.15.0)
- System.IdentityModel.Tokens.Jwt (>= 8.15.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.1.1 | 652 | 12/2/2025 |
| 2.1.0 | 647 | 12/2/2025 |
| 2.0.0 | 269 | 9/17/2025 |
| 1.7.23 | 171 | 9/8/2025 |
| 1.7.22 | 167 | 9/8/2025 |
| 1.7.21 | 179 | 8/14/2025 |
| 1.7.20 | 193 | 6/23/2025 |
| 1.7.19 | 163 | 6/3/2025 |
| 1.7.18 | 169 | 5/29/2025 |
| 1.7.17 | 185 | 5/26/2025 |
| 1.7.15 | 122 | 4/12/2025 |
| 1.7.14 | 148 | 4/11/2025 |
| 1.7.13 | 148 | 3/29/2025 |
| 1.7.12 | 161 | 3/28/2025 |
| 1.7.11 | 156 | 3/28/2025 |
| 1.7.10 | 161 | 3/28/2025 |
| 1.7.9 | 161 | 3/28/2025 |
| 1.7.8 | 146 | 3/28/2025 |
| 1.7.5 | 185 | 3/17/2025 |
| 1.7.4 | 158 | 3/16/2025 |
| 1.7.3 | 170 | 3/16/2025 |
| 1.7.2 | 179 | 3/16/2025 |
| 1.7.1 | 200 | 3/11/2025 |
| 1.6.8 | 204 | 3/11/2025 |
| 1.6.7 | 251 | 3/4/2025 |
| 1.6.6 | 137 | 2/26/2025 |
| 1.6.5 | 147 | 2/20/2025 |
| 1.6.4 | 123 | 2/20/2025 |
| 1.6.3 | 142 | 2/5/2025 |
| 1.6.2 | 134 | 1/24/2025 |
| 1.6.1 | 129 | 1/24/2025 |
| 1.6.0 | 127 | 1/16/2025 |
| 1.5.2 | 133 | 1/16/2025 |
| 1.5.1 | 172 | 11/3/2024 |
| 1.5.0 | 147 | 10/26/2024 |
| 1.3.2 | 143 | 10/24/2024 |
| 1.3.0 | 141 | 10/10/2024 |
| 1.2.5 | 152 | 10/9/2024 |
| 1.2.4 | 134 | 10/8/2024 |
| 1.2.1 | 139 | 10/3/2024 |
| 1.2.0 | 163 | 9/29/2024 |
| 1.1.1 | 140 | 9/23/2024 |
| 1.1.0 | 161 | 9/18/2024 |
| 1.0.33 | 154 | 9/15/2024 |
| 1.0.28 | 153 | 8/28/2024 |
| 1.0.27 | 180 | 8/24/2024 |
| 1.0.26 | 161 | 7/7/2024 |
| 1.0.25 | 169 | 7/6/2024 |
| 1.0.24 | 162 | 6/25/2024 |
| 1.0.23 | 178 | 6/1/2024 |
| 1.0.22 | 172 | 5/14/2024 |
| 1.0.21 | 147 | 5/14/2024 |
| 1.0.20 | 180 | 4/8/2024 |
| 1.0.19 | 160 | 4/3/2024 |
| 1.0.18 | 180 | 3/23/2024 |
| 1.0.17 | 163 | 3/19/2024 |
| 1.0.16 | 164 | 3/19/2024 |
| 1.0.15 | 191 | 3/11/2024 |
| 1.0.14 | 182 | 3/10/2024 |
| 1.0.13 | 180 | 3/6/2024 |
| 1.0.12 | 209 | 3/1/2024 |
| 1.0.11 | 166 | 3/1/2024 |
| 1.0.10 | 172 | 3/1/2024 |
| 1.0.9 | 163 | 3/1/2024 |
| 1.0.8 | 183 | 2/19/2024 |
| 1.0.7 | 175 | 2/17/2024 |
| 1.0.6 | 183 | 2/17/2024 |
| 1.0.5 | 178 | 2/17/2024 |
| 1.0.4 | 175 | 2/7/2024 |
| 1.0.3 | 174 | 2/6/2024 |
| 1.0.1 | 157 | 2/6/2024 |
| 1.0.0 | 217 | 1/9/2024 |
| 1.0.0-preview99 | 192 | 12/22/2023 |
| 1.0.0-preview98 | 157 | 12/21/2023 |
| 1.0.0-preview97 | 156 | 12/21/2023 |
| 1.0.0-preview96 | 156 | 12/20/2023 |
| 1.0.0-preview95 | 148 | 12/20/2023 |
| 1.0.0-preview94 | 153 | 12/18/2023 |
| 1.0.0-preview93 | 164 | 12/13/2023 |
| 1.0.0-preview92 | 155 | 12/13/2023 |
| 1.0.0-preview91 | 159 | 12/12/2023 |
| 1.0.0-preview90 | 164 | 12/11/2023 |
| 1.0.0-preview89 | 167 | 12/11/2023 |
| 1.0.0-preview88 | 185 | 12/6/2023 |
| 1.0.0-preview87 | 149 | 12/6/2023 |
| 1.0.0-preview86 | 165 | 12/6/2023 |
| 1.0.0-preview85 | 162 | 12/6/2023 |
| 1.0.0-preview84 | 173 | 12/5/2023 |
| 1.0.0-preview83 | 168 | 12/5/2023 |
| 1.0.0-preview82 | 160 | 12/5/2023 |
| 1.0.0-preview81 | 163 | 12/4/2023 |
| 1.0.0-preview80 | 154 | 12/1/2023 |
| 1.0.0-preview77 | 161 | 12/1/2023 |
| 1.0.0-preview76 | 156 | 12/1/2023 |
| 1.0.0-preview75 | 156 | 12/1/2023 |
| 1.0.0-preview74 | 166 | 11/26/2023 |
| 1.0.0-preview73 | 178 | 11/7/2023 |
| 1.0.0-preview72 | 163 | 11/6/2023 |
| 1.0.0-preview71 | 166 | 11/3/2023 |
| 1.0.0-preview70 | 159 | 11/2/2023 |
| 1.0.0-preview69 | 148 | 11/2/2023 |
| 1.0.0-preview68 | 159 | 11/2/2023 |
| 1.0.0-preview67 | 150 | 11/2/2023 |
| 1.0.0-preview66 | 145 | 11/2/2023 |
| 1.0.0-preview65 | 164 | 11/2/2023 |
| 1.0.0-preview64 | 165 | 11/2/2023 |
| 1.0.0-preview63 | 146 | 11/2/2023 |
| 1.0.0-preview62 | 168 | 11/1/2023 |
| 1.0.0-preview61 | 146 | 11/1/2023 |
| 1.0.0-preview60 | 166 | 11/1/2023 |
| 1.0.0-preview59 | 155 | 11/1/2023 |
| 1.0.0-preview58 | 160 | 10/31/2023 |
| 1.0.0-preview57 | 164 | 10/31/2023 |
| 1.0.0-preview56 | 156 | 10/31/2023 |
| 1.0.0-preview55 | 162 | 10/31/2023 |
| 1.0.0-preview54 | 145 | 10/31/2023 |
| 1.0.0-preview53 | 133 | 10/31/2023 |
| 1.0.0-preview52 | 156 | 10/31/2023 |
| 1.0.0-preview51 | 148 | 10/31/2023 |
| 1.0.0-preview50 | 161 | 10/31/2023 |
| 1.0.0-preview48 | 159 | 10/31/2023 |
| 1.0.0-preview46 | 169 | 10/31/2023 |
| 1.0.0-preview45 | 144 | 10/31/2023 |
| 1.0.0-preview44 | 157 | 10/31/2023 |
| 1.0.0-preview43 | 150 | 10/31/2023 |
| 1.0.0-preview42 | 155 | 10/30/2023 |
| 1.0.0-preview41 | 157 | 10/30/2023 |
| 1.0.0-preview40 | 166 | 10/27/2023 |
| 1.0.0-preview39 | 168 | 10/27/2023 |
| 1.0.0-preview38 | 155 | 10/27/2023 |
| 1.0.0-preview37 | 171 | 10/27/2023 |
| 1.0.0-preview36 | 151 | 10/27/2023 |
| 1.0.0-preview35 | 140 | 10/27/2023 |
| 1.0.0-preview34 | 143 | 10/27/2023 |
| 1.0.0-preview33 | 155 | 10/26/2023 |
| 1.0.0-preview32 | 165 | 10/26/2023 |
| 1.0.0-preview101 | 145 | 1/5/2024 |