Authentication
Htmx.Components provides seamless integration with ASP.NET Core authentication and includes special handling for HTMX requests.
Basic Authentication Setup
Htmx.Components works with any ASP.NET Core authentication scheme. Here's a real example from CruSibyl.Web using OpenID Connect:
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(oidc =>
{
oidc.ClientId = builder.Configuration["Authentication:ClientId"];
oidc.ClientSecret = builder.Configuration["Authentication:ClientSecret"];
oidc.Authority = builder.Configuration["Authentication:Authority"];
oidc.ResponseType = OpenIdConnectResponseType.Code;
oidc.Scope.Add("openid");
oidc.Scope.Add("profile");
oidc.Scope.Add("email");
oidc.Scope.Add("eduPerson");
oidc.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
};
// Configure HTMX-specific authentication handling
oidc.ConfigureHtmxAuthPopup("/auth/popup-login");
oidc.AddIamFallback();
});
// Configure Htmx.Components with authentication
builder.Services.AddHtmxComponents(htmxOptions =>
{
htmxOptions.WithUserIdClaimType("your-user-id-claim-type");
// ... other options
});
// Configure middleware
app.UseHtmxPageState(); // Important: Before authentication
app.UseAuthentication();
app.UseAuthorization();
HTMX Authentication Popup
Htmx.Components includes special handling for authentication in HTMX requests. When an unauthenticated HTMX request is made, instead of redirecting the entire page, it can show a popup for authentication.
Popup Login Controller
[Authorize]
public class AuthController : Controller
{
[Authorize]
[AuthStatusUpdate] // This attribute updates the AuthStatus component after login
[HttpGet("/auth/login")]
public IActionResult Login()
{
// If this executes, the user is already authenticated
return Ok();
}
[Authorize]
[HttpGet("/auth/popup-login")]
public IActionResult PopupLogin()
{
// Return a view that posts a message to the parent window and closes itself
return View();
}
}
Popup Login View
Create Views/Auth/PopupLogin.cshtml
:
<!DOCTYPE html>
<html>
<head>
<title>Login Success</title>
</head>
<body>
<script>
// Notify the opener window and close the popup
window.opener?.postMessage('login-success', '*');
window.close();
</script>
</body>
</html>
AuthStatus Component
The AuthStatus
component displays the current user's authentication status and provides login/logout functionality.
Basic Usage
Include the component in your layout:
<div class="navbar-end">
@await Component.InvokeAsync("NavBar")
@await Component.InvokeAsync("AuthStatus")
</div>
AuthStatusUpdate Attribute
Use the [AuthStatusUpdate]
attribute on actions that change authentication state to automatically refresh the AuthStatus component:
[Authorize]
[AuthStatusUpdate]
[HttpGet("/auth/login")]
public IActionResult Login()
{
return Ok();
}
[AuthStatusUpdate]
[HttpPost("/auth/logout")]
public IActionResult Logout()
{
return SignOut(CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
Authorization with Navigation
Navigation items are automatically filtered based on user permissions:
[Route("Admin")]
[NavActionGroup(DisplayName = "Admin", Icon = "fas fa-cogs", Order = 2)]
public class AdminController : Controller
{
[HttpGet("AdminUsers")]
[Authorize(Policy = "SystemAccess")]
[NavAction(DisplayName = "Admin Users", Icon = "fas fa-users-cog", Order = 1)]
public async Task<IActionResult> AdminUsers()
{
// Only users with SystemAccess policy can see and access this
return Ok(tableModel);
}
}
User Claims Configuration
Configure which claim type contains the user ID:
builder.Services.AddHtmxComponents(htmxOptions =>
{
htmxOptions.WithUserIdClaimType("sub"); // or whatever your user ID claim is
});
Cookie Authentication Example
For simpler scenarios, you can use cookie authentication:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Auth/Login";
options.LogoutPath = "/Auth/Logout";
options.AccessDeniedPath = "/Auth/AccessDenied";
});
How It Works
- Automatic Detection: Htmx.Components automatically detects when HTMX requests need authentication
- Popup Authentication: Instead of redirecting the entire page, authentication can happen in a popup
- Status Updates: The
AuthStatus
component automatically updates when authentication state changes - Navigation Filtering: Navigation items are filtered based on user authorization
- Seamless Integration: Works with any ASP.NET Core authentication provider
Best Practices
- Claim Configuration: Configure the correct user ID claim type for your identity provider
- Authorization Attributes: Use standard ASP.NET Core authorization attributes on controllers and actions
- Status Updates: Use
[AuthStatusUpdate]
on actions that change authentication state
Next Steps
Authorization: Learn about setting up authorization policies and permissions
Navigation: Understand how navigation integrates with authentication
Tables: See how tables respect authorization rules foreach (var role in roles) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); }
return identity; }
### User Service Integration
Create a user service for profile management:
```csharp
public interface IUserService
{
Task<UserProfile> GetUserProfileAsync(string userId);
Task<bool> UpdateProfileAsync(string userId, UserProfile profile);
}
public class UserService : IUserService
{
private readonly ApplicationDbContext _context;
public UserService(ApplicationDbContext context)
{
_context = context;
}
public async Task<UserProfile> GetUserProfileAsync(string userId)
{
var user = await _context.Users
.Where(u => u.Id == userId)
.Select(u => new UserProfile
{
Id = u.Id,
DisplayName = u.DisplayName,
Email = u.Email,
AvatarUrl = u.AvatarUrl,
LastLoginAt = u.LastLoginAt
})
.FirstOrDefaultAsync();
return user ?? new UserProfile();
}
}
Multi-Tenant Authentication
Tenant Resolution
Implement tenant resolution for multi-tenant applications:
public class TenantAuthStatusProvider : IAuthStatusProvider
{
private readonly ITenantService _tenantService;
public async Task<AuthStatusViewModel> GetAuthStatusAsync(ClaimsPrincipal user)
{
if (user?.Identity?.IsAuthenticated != true)
return new AuthStatusViewModel { IsAuthenticated = false };
var tenantId = user.FindFirst("TenantId")?.Value;
var tenant = await _tenantService.GetTenantAsync(tenantId);
return new AuthStatusViewModel
{
IsAuthenticated = true,
UserName = $"{user.Identity.Name} ({tenant?.Name})",
ProfileImageUrl = tenant?.LogoUrl
};
}
}
Security Considerations
CSRF Protection
HTMX Components automatically handles CSRF tokens:
<!-- CSRF tokens are automatically included in HTMX requests -->
<button hx-post="/api/secure-action">Secure Action</button>
Session Security
Configure secure session options:
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
Content Security Policy
Configure CSP for HTMX compatibility:
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://unpkg.com; " +
"style-src 'self' 'unsafe-inline';");
await next();
});
Troubleshooting
Authentication Loops
If users get stuck in authentication loops:
- Check that
UseHtmxPageState()
is called before authentication middleware - Verify authentication scheme configuration
- Ensure proper HTMX headers are being sent
HTMX Auth Popup Not Working
- Verify the popup configuration is correct
- Check that JavaScript event handlers are registered
- Ensure popup blockers aren't interfering
Claims Not Available
- Check that claims are properly added during authentication
- Verify claim transformations are working
- Ensure user service is correctly resolving user data