I am trying to integrate authentication with our LDAP server for .NET 8 Blazor Web App.
My launchSettings.json
{"$schema": "http://json.schemastore.org/launchsettings.json","iisSettings": {"windowsAuthentication": false,"anonymousAuthentication": true,"iisExpress": {"applicationUrl": "http://localhost:25412","sslPort": 44310,"environmentVariables": {"UserDomains": "mycorp=LDAP://mycorp.com" } } },"profiles": {"http": {"commandName": "Project","dotnetRunMessages": true,"launchBrowser": true,"applicationUrl": "http://localhost:5102","environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development","UserDomains": "mycorp=LDAP://mycorp.com" } },"https": {"commandName": "Project","dotnetRunMessages": true,"launchBrowser": true,"applicationUrl": "https://localhost:7038;http://localhost:5102","environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development","UserDomains": "mycorp=LDAP://mycorp.com" } },"IIS Express": {"commandName": "IISExpress","launchBrowser": true,"environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development","UserDomains": "mycorp=LDAP://mycorp.com" } } } }UserLogin.razor
@page "/userlogin"@using System.ComponentModel.DataAnnotations@using System.Text@using System.DirectoryServices@using RCBuisinessLogic@using RCBuisinessLogic.Authentication@using RCWebApp.Models@rendermode InteractiveServer@inject IHttpContextAccessor HttpContextAccessor@inject NavigationManager NavigationManager@inject UserInformation UserInformation<div class="user-login" style="height: 630px;"><EditForm Model="@Login" OnSubmit="HandleLogin" FormName="UserLoginForm"><DataAnnotationsValidator /><ValidationSummary /><div class="form-group"><label for="UserName">Username:</label><InputText id="UserName" class="form-control" @bind-Value="Login.UserName" /><ValidationMessage For="@(() => Login.UserName)" /></div><div class="form-group"><label for="Password">Password:</label><InputText id="Password" class="form-control" @bind-Value="Login.Password" Type="password" /><ValidationMessage For="@(() => Login.Password)" /></div><button type="submit" class="btn btn-primary">Login</button></EditForm></div>@code { private Login Login { get; set; } = new Login(); private async Task HandleLogin() { string userDomains = Environment.GetEnvironmentVariable("UserDomains"); bool isValidLogin = IsValidLogin("LDAP://mycorp.com", "mycorp", Login.UserName, Login.Password, out string retMessage); if (isValidLogin) { NavigationManager.NavigateTo("/dashboard"); } else { NavigationManager.NavigateTo("/"); } } private bool IsValidLogin(string LDAPPath, string domainName, string userName, string password, out string retMessage) { bool returnValue = false; retMessage = null; try { // encode user input before being used in LDAP query. string safeUserName = EscapeLdapSearchFilter(userName); var userClaims = HttpContextAccessor.HttpContext?.User?.Claims; bool isAuthenticated = HttpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false; string email = HttpContextAccessor.HttpContext?.User?. FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value; var de = new DirectoryEntry(LDAPPath, userName, password); using (var ds = new DirectorySearcher(de) { Filter = "samaccountname=" + safeUserName }) { SearchResult sr = ds.FindOne(); if (sr == null) { retMessage = "Invalid Login."; } else { string userID = UserInformation.GetByName($"{domainName}\\{userName}", email); returnValue = true; } } } catch (Exception ex) { retMessage = $"Error during LDAP login: {ex.Message}"; } return returnValue; } private static string EscapeLdapSearchFilter(string searchFilter) { StringBuilder escape = new StringBuilder(); for (int i = 0; i < searchFilter.Length; i++) { char current = searchFilter[i]; switch (current) { case '\\': escape.Append(@"\5c"); break; case '*': escape.Append(@"\2a"); break; case '(': escape.Append(@"\28"); break; case ')': escape.Append(@"\29"); break; case '\u0000': escape.Append(@"\00"); break; case '/': escape.Append(@"\2f"); break; default: escape.Append(current); break; } } return escape.ToString(); }}userClaims is empty, isAuthenticated is false, email is null in the below code from above file
var userClaims = HttpContextAccessor.HttpContext?.User?.Claims; bool isAuthenticated = HttpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false; string email = HttpContextAccessor.HttpContext?.User?. FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;My Program.csIt has some Auth configuration I tried, but it didn't work, so that's commented.
using Microsoft.AspNetCore.Authentication;using RCBuisinessLogic.Authentication;using RCBuisinessLogic.DataAccess;using RCWebApp.Components;var builder = WebApplication.CreateBuilder(args);builder.Services.AddHttpContextAccessor();// Register IConfigurationbuilder.Services.AddSingleton<IConfiguration>(builder.Configuration);builder.Services.AddSingleton<BaseDAL>();// Register AuthenticationConfigurationbuilder.Services.AddSingleton<AuthenticationConfiguration>();// Register UserInformationbuilder.Services.AddTransient<UserInformation>();// Add services to the container.builder.Services.AddRazorComponents() .AddInteractiveServerComponents();// Add authentication services and configure the custom authentication scheme.//builder.Services.AddAuthentication(options =>//{// options.DefaultAuthenticateScheme = "LDAP";// options.DefaultChallengeScheme = "LDAP";//})//.AddScheme<AuthenticationSchemeOptions, LdapAuthenticationHandler>("LDAP", options => { });// Add authorization services//builder.Services.AddAuthorization(options =>//{// // You can configure authorization policies here if needed.// options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser());//});var app = builder.Build();// Use authentication and authorization before handling routingapp.UseAuthentication();app.UseAuthorization();//app.Use(async (context, next) =>//{// var user = context.User;// // Only redirect authenticated users if they are NOT trying to access '/userlogin'// if (user.Identity.IsAuthenticated && !context.Request.Path.StartsWithSegments("/userlogin"))// {// // Redirect authenticated users to the dashboard or another page// context.Response.Redirect("/dashboard");// return;// }// await next(); // Continue the request pipeline//});// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseAntiforgery();app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();app.Run();Tried out this middleware suggested by chatgpt but, it didn't work.
using Microsoft.AspNetCore.Authentication;using Microsoft.Extensions.Options;using RCBuisinessLogic.Authentication;using System.DirectoryServices;using System.Security.Claims;using System.Text.Encodings.Web;using System.Threading.Tasks;public class LdapAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>{ private readonly UserInformation _userInformation; public LdapAuthenticationHandler( IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, UserInformation userInformation) : base(options, logger, encoder, clock) { _userInformation = userInformation; } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { var username = Context.Request.Form["username"]; var password = Context.Request.Form["password"]; string domain = "mycorp.com"; // Adjust to your LDAP domain // Perform LDAP authentication here. var isAuthenticated = AuthenticateWithLdap(username, password, domain, out var userId); if (isAuthenticated) { // Create claims and set the principal var claims = new List<Claim> { new Claim(ClaimTypes.Name, username), new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, userId) // Adjust according to your LDAP setup }; var claimsIdentity = new ClaimsIdentity(claims, "LDAP"); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); var ticket = new AuthenticationTicket(claimsPrincipal, "LDAP"); return AuthenticateResult.Success(ticket); } return AuthenticateResult.Fail("Invalid username or password."); } private bool AuthenticateWithLdap(string username, string password, string domain, out string userId) { userId = null; try { var ldapPath = $"LDAP://{domain}"; using var de = new DirectoryEntry(ldapPath, username, password); using var ds = new DirectorySearcher(de) { Filter = $"(sAMAccountName={username})" }; var result = ds.FindOne(); if (result != null) { userId = result.Properties["sAMAccountName"][0].ToString(); // Or other user identifier return true; } } catch { // Log error or handle it } return false; }}