Bielu.AspNetCore.Identity.Ldap.OpenTelemetry
1.0.0-beta.639173969515959510
Prefix Reserved
dotnet add package Bielu.AspNetCore.Identity.Ldap.OpenTelemetry --version 1.0.0-beta.639173969515959510
NuGet\Install-Package Bielu.AspNetCore.Identity.Ldap.OpenTelemetry -Version 1.0.0-beta.639173969515959510
<PackageReference Include="Bielu.AspNetCore.Identity.Ldap.OpenTelemetry" Version="1.0.0-beta.639173969515959510" />
<PackageVersion Include="Bielu.AspNetCore.Identity.Ldap.OpenTelemetry" Version="1.0.0-beta.639173969515959510" />
<PackageReference Include="Bielu.AspNetCore.Identity.Ldap.OpenTelemetry" />
paket add Bielu.AspNetCore.Identity.Ldap.OpenTelemetry --version 1.0.0-beta.639173969515959510
#r "nuget: Bielu.AspNetCore.Identity.Ldap.OpenTelemetry, 1.0.0-beta.639173969515959510"
#:package Bielu.AspNetCore.Identity.Ldap.OpenTelemetry@1.0.0-beta.639173969515959510
#addin nuget:?package=Bielu.AspNetCore.Identity.Ldap.OpenTelemetry&version=1.0.0-beta.639173969515959510&prerelease
#tool nuget:?package=Bielu.AspNetCore.Identity.Ldap.OpenTelemetry&version=1.0.0-beta.639173969515959510&prerelease
Bielu.AspNetCore.Identity.Ldap
Bielu.AspNetCore.Identity.Ldap is an ASP.NET Core Identity provider for LDAP directories. Authenticate users and resolve roles directly against any LDAP-compatible server (OpenLDAP, Active Directory, 389 Directory Server, etc.) without a local database.
Part of the bielu ecosystem — a collection of open-source .NET libraries for search, messaging, observability, and infrastructure.
⚠️ Note: Pre version 1.0.0, the API is regarded as unstable and breaking changes may be introduced.
Key Features
- ✅ Drop-in ASP.NET Core Identity stores —
IUserStore<LdapUser>,IRoleStore<LdapRole>,IUserPasswordStore, andIUserEmailStoreimplementations - ✅ Composable API —
AddLdapStoresextendsIdentityBuilder(likeAddEntityFrameworkStores), or useAddLdapServicesfor mixed EF + LDAP scenarios - ✅ Cross-platform — built on
System.DirectoryServices.Protocols, no Windows-only COM or Novell dependencies - ✅ OpenTelemetry instrumentation — optional package adds tracing spans and metrics counters/histograms to every LDAP operation via the Scrutor decorator pattern
- ✅ IOptionsMonitor support — hot-reload of LDAP settings when using configuration providers with change notifications
- ✅ Overridable configuration section key — defaults to
"Ldap"(LdapOptions.SectionName), callers can pass any section name - ✅ Blazor compatible — works with Blazor Server, Blazor WebAssembly (hosted), and Blazor Web App (interactive server)
- ✅ Static analysis — enforced via Bielu.StaticCode.Analyzers,
Microsoft.CodeAnalysis.PublicApiAnalyzers, andMicrosoft.VisualStudio.Threading.Analyzers
Installation
Install the packages from NuGet:
# Core package
dotnet add package Bielu.AspNetCore.Identity.Ldap
# (Optional) OpenTelemetry instrumentation
dotnet add package Bielu.AspNetCore.Identity.Ldap.OpenTelemetry
Packages
| Package | Description |
|---|---|
Bielu.AspNetCore.Identity.Ldap |
Core identity stores, ILdapService, models, and DI registration |
Bielu.AspNetCore.Identity.Ldap.OpenTelemetry |
OpenTelemetry tracing & metrics decorator for LDAP operations |
Getting Started
1. Configure appsettings.json
Add the LDAP section to your appsettings.json:
{
"Ldap": {
"Host": "ldap.example.com",
"Port": 389,
"UsesSsl": false,
"BindDn": "cn=readonly,dc=example,dc=com",
"BindPassword": "secret",
"UserSearchBase": "ou=People,dc=example,dc=com",
"UserSearchFilter": "(&(objectClass=person)(uid={0}))",
"GroupSearchBase": "ou=Groups,dc=example,dc=com",
"GroupSearchFilter": "(&(objectClass=groupOfNames)(member={0}))",
"EmailAttribute": "mail",
"UsernameAttribute": "uid",
"DisplayNameAttribute": "cn",
"GroupNameAttribute": "cn",
"ConnectionTimeoutSeconds": 30
}
}
2. Register Services
The library provides three levels of registration depending on your scenario:
Option A — LDAP-only (convenience shortcut)
When LDAP is the only identity store, use AddLdapIdentity which calls AddIdentity<LdapUser, LdapRole>() + AddLdapStores() in one call:
using Bielu.AspNetCore.Identity.Ldap.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Calls AddIdentity + registers LdapOptions, ILdapService, and LDAP stores
builder.Services.AddLdapIdentity(builder.Configuration);
You can override the configuration section key:
builder.Services.AddLdapIdentity(builder.Configuration, sectionName: "MyApp:Directory");
Or use the fluent API:
builder.Services.AddLdapIdentity(options =>
{
options.Host = "ldap.example.com";
options.Port = 636;
options.UsesSsl = true;
options.BindDn = "cn=readonly,dc=example,dc=com";
options.BindPassword = "secret";
options.UserSearchBase = "ou=People,dc=example,dc=com";
options.UserSearchFilter = "(&(objectClass=person)(uid={0}))";
options.GroupSearchBase = "ou=Groups,dc=example,dc=com";
options.GroupSearchFilter = "(&(objectClass=groupOfNames)(member={0}))";
});
Option B — Build on top of AddIdentity (composable)
When you want to control the Identity pipeline yourself — just like AddEntityFrameworkStores<TContext>() — use AddLdapStores as an extension on IdentityBuilder:
using Bielu.AspNetCore.Identity.Ldap;
using Bielu.AspNetCore.Identity.Ldap.Extensions;
builder.Services
.AddIdentity<LdapUser, LdapRole>(options =>
{
// Configure Identity options (lockout, password rules, etc.)
options.SignIn.RequireConfirmedAccount = false;
})
.AddLdapStores(builder.Configuration); // registers LdapOptions, ILdapService, and stores
This is the recommended pattern when you need to customize Identity options or chain additional Identity extensions.
Option C — Mixed stores (EF + LDAP)
When Entity Framework (or another provider) owns the Identity stores and you want to use LDAP only for credential validation, use AddLdapServices which registers LdapOptions and ILdapService without replacing the Identity stores:
using Bielu.AspNetCore.Identity.Ldap.Abstractions;
using Bielu.AspNetCore.Identity.Ldap.Extensions;
// EF owns the Identity stores
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
// Register LDAP as a service (no stores — just ILdapService for credential validation)
builder.Services.AddLdapServices(builder.Configuration);
Then inject ILdapService to validate LDAP credentials against your existing EF users:
public class HybridLoginService
{
private readonly ILdapService _ldap;
private readonly UserManager<ApplicationUser> _users;
private readonly SignInManager<ApplicationUser> _signIn;
public HybridLoginService(
ILdapService ldap,
UserManager<ApplicationUser> users,
SignInManager<ApplicationUser> signIn)
{
_ldap = ldap;
_users = users;
_signIn = signIn;
}
public async Task<SignInResult> LoginAsync(string username, string password)
{
// Try LDAP first
if (await _ldap.ValidateCredentialsAsync(username, password))
{
// Find or create a local EF user linked to the LDAP account
var user = await _users.FindByNameAsync(username);
if (user is null)
{
var ldapEntry = await _ldap.FindUserAsync(username);
user = new ApplicationUser
{
UserName = username,
Email = ldapEntry?.GetAttribute("mail"),
AuthenticationSource = "LDAP",
};
var createResult = await _users.CreateAsync(user);
if (!createResult.Succeeded)
{
return SignInResult.Failed;
}
}
await _signIn.SignInAsync(user, isPersistent: false);
return SignInResult.Success;
}
// Fall back to local password
return await _signIn.PasswordSignInAsync(username, password, false, false);
}
}
This pattern lets you:
- Use EF stores for local accounts, OpenID Connect external logins, and user management
- Use LDAP for credential validation against a corporate directory
- Gradually migrate users from LDAP to local accounts
Examples
Blazor Server / Blazor Web App (Interactive Server)
A complete Blazor Server example with LDAP authentication, AuthenticationStateProvider, and role-based [Authorize]:
Program.cs:
using Bielu.AspNetCore.Identity.Ldap;
using Bielu.AspNetCore.Identity.Ldap.Extensions;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
var builder = WebApplication.CreateBuilder(args);
// 1. Register Identity with LDAP stores (builds on top of AddIdentity)
builder.Services
.AddIdentity<LdapUser, LdapRole>()
.AddLdapStores(builder.Configuration);
// 2. Add authentication & authorization
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme);
builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();
// 3. Add Blazor services
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
Components/Pages/Login.razor:
Note: In a Blazor interactive-server component,
SignInManager.PasswordSignInAsynccannot set the auth cookie (the HTTP response has already started over SignalR). The recommended pattern is to POST to a minimal API endpoint that sets the cookie, then redirect — see the example app'sAuthEndpoints.csandLogin.razorfor a working implementation.
@page "/login"
@inject NavigationManager Navigation
<PageTitle>Login</PageTitle>
<h3>LDAP Login</h3>
<form method="post" action="/login">
<AntiforgeryToken />
<div class="mb-3">
<label>Username</label>
<input name="username" class="form-control" />
</div>
<div class="mb-3">
<label>Password</label>
<input name="password" type="password" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Sign In</button>
</form>
Components/Pages/SecurePage.razor:
@page "/secure"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
<PageTitle>Secure Page</PageTitle>
<h3>Welcome, @context.User.Identity?.Name!</h3>
<p>You are authenticated via LDAP.</p>
<AuthorizeView Roles="Administrators">
<Authorized>
<p>🔑 You have the <strong>Administrators</strong> role.</p>
</Authorized>
</AuthorizeView>
@code {
[CascadingParameter]
private Task<AuthenticationState> authStateTask { get; set; } = default!;
private AuthenticationState context = default!;
protected override async Task OnInitializedAsync()
{
context = await authStateTask;
}
}
Blazor WebAssembly (Hosted)
For Blazor WASM with a hosted ASP.NET Core backend, register LDAP identity on the server project and expose an authentication API:
Server/Program.cs:
using Bielu.AspNetCore.Identity.Ldap;
using Bielu.AspNetCore.Identity.Ldap.Extensions;
using Microsoft.AspNetCore.Identity;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddIdentity<LdapUser, LdapRole>()
.AddLdapStores(builder.Configuration);
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme);
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Minimal API login endpoint for the WASM client
app.MapPost("/api/login", async (
LoginRequest request,
SignInManager<LdapUser> signInManager) =>
{
var result = await signInManager.PasswordSignInAsync(
request.Username, request.Password,
isPersistent: false, lockoutOnFailure: false);
return result.Succeeded
? Results.Ok()
: Results.Unauthorized();
});
app.MapPost("/api/logout", async (SignInManager<LdapUser> signInManager) =>
{
await signInManager.SignOutAsync();
return Results.Ok();
});
app.MapGet("/api/me", (HttpContext context) =>
{
if (context.User.Identity?.IsAuthenticated != true)
return Results.Unauthorized();
return Results.Ok(new
{
Name = context.User.Identity.Name,
Roles = context.User.Claims
.Where(c => c.Type == System.Security.Claims.ClaimTypes.Role)
.Select(c => c.Value)
});
}).RequireAuthorization();
app.MapRazorPages();
app.MapFallbackToFile("index.html");
app.Run();
record LoginRequest(string Username, string Password);
Minimal API
A simple Minimal API example for non-Blazor scenarios:
using Bielu.AspNetCore.Identity.Ldap;
using Bielu.AspNetCore.Identity.Ldap.Extensions;
using Microsoft.AspNetCore.Identity;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddIdentity<LdapUser, LdapRole>()
.AddLdapStores(builder.Configuration);
builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme)
.AddCookie(IdentityConstants.ApplicationScheme);
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapPost("/login", async (LoginRequest req, SignInManager<LdapUser> signIn) =>
{
var result = await signIn.PasswordSignInAsync(
req.Username, req.Password, false, false);
return result.Succeeded ? Results.Ok() : Results.Unauthorized();
});
app.MapGet("/me", (HttpContext ctx) => Results.Ok(new
{
Name = ctx.User.Identity?.Name
})).RequireAuthorization();
app.Run();
record LoginRequest(string Username, string Password);
Main Types
The main types provided by this library are:
| Type | Description |
|---|---|
LdapOptions |
Configuration options for LDAP connection, search bases, filters, and attribute mappings |
LdapUser |
ASP.NET Core Identity user backed by an LDAP entry (IdentityUser subclass) |
LdapRole |
ASP.NET Core Identity role backed by an LDAP group (IdentityRole subclass) |
LdapEntry |
Lightweight, read-only representation of an LDAP directory entry |
ILdapService |
High-level LDAP operations: credential validation, user/group search |
LdapUserStore |
IUserStore<LdapUser>, IUserPasswordStore, IUserEmailStore implementation |
LdapRoleStore |
IRoleStore<LdapRole> implementation |
OpenTelemetryLdapServiceDecorator |
ILdapService decorator adding tracing and metrics |
LdapActivitySource |
ActivitySource and span name constants for tracing |
LdapMetrics |
Meter, counters, and histograms for LDAP metrics |
Configuration Reference
All properties live on LdapOptions:
| Property | Type | Default | Description |
|---|---|---|---|
Host |
string |
"localhost" |
LDAP server hostname or IP |
Port |
int |
389 |
LDAP port (389 plain, 636 SSL) |
UsesSsl |
bool |
false |
Use SSL/TLS |
BindDn |
string? |
null |
Service-account DN for search operations (anonymous if null) |
BindPassword |
string? |
null |
Password for BindDn |
UserSearchBase |
string |
"" |
Base DN for user searches |
UserSearchFilter |
string |
"(&(objectClass=person)(uid={0}))" |
Filter template — {0} = username |
GroupSearchBase |
string |
"" |
Base DN for group searches |
GroupSearchFilter |
string |
"(&(objectClass=groupOfNames)(member={0}))" |
Filter template — {0} = user DN |
EmailAttribute |
string |
"mail" |
Attribute containing user email |
UsernameAttribute |
string |
"uid" |
Attribute containing username |
DisplayNameAttribute |
string |
"cn" |
Attribute containing display name |
GroupNameAttribute |
string |
"cn" |
Attribute containing group/role name |
ConnectionTimeoutSeconds |
int |
30 |
TCP connection timeout |
The default section key is exposed as a constant:
public const string SectionName = "Ldap"; // LdapOptions.SectionName
Windows Active Directory
For Windows AD, override the default attribute names and filters:
{
"Ldap": {
"Host": "dc.corp.example.com",
"Port": 389,
"BindDn": "CN=ServiceAccount,CN=Users,DC=corp,DC=example,DC=com",
"BindPassword": "",
"UserSearchBase": "CN=Users,DC=corp,DC=example,DC=com",
"UserSearchFilter": "(&(objectClass=user)(sAMAccountName={0}))",
"GroupSearchBase": "CN=Users,DC=corp,DC=example,DC=com",
"GroupSearchFilter": "(&(objectClass=group)(member={0}))",
"UsernameAttribute": "sAMAccountName",
"DisplayNameAttribute": "displayName",
"EmailAttribute": "mail",
"GroupNameAttribute": "cn",
"ConnectionTimeoutSeconds": 10
}
}
OpenTelemetry Instrumentation
Add the OpenTelemetry decorator after registering the core LDAP identity provider:
using Bielu.AspNetCore.Identity.Ldap;
using Bielu.AspNetCore.Identity.Ldap.Extensions;
using Bielu.AspNetCore.Identity.Ldap.OpenTelemetry.Extensions;
// 1. Register Identity with LDAP stores
builder.Services
.AddIdentity<LdapUser, LdapRole>()
.AddLdapStores(builder.Configuration);
// 2. Wrap with OpenTelemetry instrumentation (decorator pattern via Scrutor)
builder.Services.AddLdapOpenTelemetryInstrumentation();
// 3. Wire up OTel exporters
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddLdapIdentityInstrumentation() // subscribe to LDAP ActivitySource
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddLdapIdentityMetrics() // subscribe to LDAP Meter
.AddOtlpExporter());
Exported Metrics
| Metric | Type | Unit | Description |
|---|---|---|---|
ldap.identity.authentication.attempts |
Counter | — | Total authentication attempts |
ldap.identity.authentication.successes |
Counter | — | Successful authentications |
ldap.identity.authentication.failures |
Counter | — | Failed authentications |
ldap.identity.authentication.duration |
Histogram | s |
Authentication duration |
ldap.identity.search.operations |
Counter | — | Total search operations |
ldap.identity.search.duration |
Histogram | s |
Search operation duration |
ldap.identity.operation.errors |
Counter | — | Total operation errors |
Trace Spans
| Operation | Span Name |
|---|---|
| Credential validation | ldap.validate_credentials |
| Find user by username | ldap.find_user |
| Find user by DN | ldap.find_user_by_dn |
| Search users | ldap.find_users |
| List groups | ldap.find_groups |
| Get user groups | ldap.get_user_groups |
Span attributes include ldap.username, ldap.user_dn, and ldap.result_count where applicable.
Testing
Unit Tests
dotnet test test/Bielu.AspNetCore.Identity.Ldap.Tests
Unit tests cover:
LdapOptions— defaults, section name constant, property assignmentLdapEntry— case-insensitive attribute lookup, null guardsLdapUserStore/LdapRoleStore— find/lookup behaviour, unsupported-operation guards for create/update/deleteOpenTelemetryLdapServiceDecorator— delegation, exception propagation, ActivitySource/Meter metadataServiceRegistrationTests— bothIConfigurationand fluent API overloads, custom section keys, null guards, OTel decorator wrapping
Windows Integration Tests
Integration tests connect to a real Windows Active Directory server. They are skipped automatically unless explicitly enabled:
# Set the opt-in flag
export LDAP_INTEGRATION=true
# Override credentials via environment variables (use __ for nesting)
export Ldap__Host=dc.corp.example.com
export Ldap__BindDn="CN=ServiceAccount,CN=Users,DC=corp,DC=example,DC=com"
export Ldap__BindPassword="password"
dotnet test test/Bielu.AspNetCore.Identity.Ldap.Windows.Tests
Configuration is loaded from appsettings.Integration.json with standard __-separated environment variable overrides (e.g. Ldap__Host, Integration__TestUsername).
Project Structure
├── src/
│ ├── Bielu.AspNetCore.Identity.Ldap/
│ │ ├── Abstractions/ # ILdapService, LdapEntry
│ │ ├── Extensions/ # DI registration (IConfiguration + fluent API)
│ │ ├── Internal/ # LdapService implementation
│ │ ├── Stores/ # LdapUserStore, LdapRoleStore
│ │ ├── LdapOptions.cs # Configuration with SectionName constant
│ │ ├── LdapUser.cs # Identity user model
│ │ └── LdapRole.cs # Identity role model
│ │
│ ├── Bielu.AspNetCore.Identity.Ldap.OpenTelemetry/
│ │ ├── Extensions/ # OTel DI registration
│ │ ├── Instrumentation/ # OpenTelemetryLdapServiceDecorator
│ │ ├── LdapActivitySource.cs # ActivitySource + span names
│ │ └── LdapMetrics.cs # Meter + counters/histograms
│ │
│ └── Bielu.AspNetCore.Identity.slnx
│
├── test/
│ ├── Bielu.AspNetCore.Identity.Ldap.Tests/ # Unit tests (xUnit + Shouldly + NSubstitute)
│ └── Bielu.AspNetCore.Identity.Ldap.Windows.Tests/ # Windows AD integration tests
│
├── Directory.Packages.props # Central package management
├── version.props # Version configuration
└── LICENSE # MIT License
Contributing
We welcome contributions! Feel free to get involved by opening issues or submitting pull requests.
Related Bielu Projects
| Project | Description |
|---|---|
| Bielu.StaticCode.Analyzers | Static analysis rules used across bielu packages |
| Bielu.AspNetCore.AsyncApi | Code-first AsyncAPI documentation generator for .NET |
| Bielu.PersistentQueues | Fast persistent queues for .NET |
| Bielu.Common.Libraries | Shared utilities and abstractions for the bielu ecosystem |
Acknowledgments
This project builds upon:
- ASP.NET Core Identity — Microsoft's pluggable authentication and authorization framework
- System.DirectoryServices.Protocols — Microsoft's cross-platform LDAP client
- Scrutor — Assembly scanning and decoration extensions for
IServiceCollection - OpenTelemetry .NET — Observability framework for traces and metrics
License
This project is licensed under the MIT License — see the LICENSE file for details.
| 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
- Bielu.AspNetCore.Identity.Ldap (>= 1.0.0-beta.639173969515959510)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- OpenTelemetry.Api (>= 1.16.0)
- Scrutor (>= 7.0.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 |
|---|---|---|
| 1.0.0-beta.639173969515959510 | 102 | 6/18/2026 |