Compare commits

..

18 Commits

Author SHA1 Message Date
b08d88ee0f Configure NLog only in non-development environments
Refactored logging setup to clear providers and use NLog exclusively when not in development. This ensures development uses default logging, while production and other environments leverage NLog.
2026-03-18 11:14:26 +01:00
58f228dc6f Add NLog file logging configuration and error handling
Integrate NLog for structured file-based logging by adding configuration to appsettings.json and initializing NLog in Program.cs. Wrap application startup in a try-catch block to log unhandled exceptions. No changes to core application logic.
2026-03-18 11:12:16 +01:00
a68cb68c5c Add NLog and NLog.Web.AspNetCore dependencies
Included NLog (6.1.1) and NLog.Web.AspNetCore (6.1.2) NuGet packages
to enable advanced logging and ASP.NET Core integration.
2026-03-18 11:12:00 +01:00
f78b8dfd9e Bump version to 1.0.1-beta in project file
Updated FakeNTLMServer.csproj to increment the Version, AssemblyVersion, FileVersion, and InformationalVersion properties from 1.0.0-beta to 1.0.1-beta. This reflects a new pre-release version of the project.
2026-03-18 09:45:00 +01:00
eac1ab3929 Revert "Enable Windows Authentication in web.config"
This reverts commit 81c6604295.
2026-03-18 09:41:56 +01:00
665b44ad82 Revert "Enable anonymous authentication alongside Windows auth"
This reverts commit 32ff0a4888.
2026-03-18 09:41:35 +01:00
32ff0a4888 Enable anonymous authentication alongside Windows auth
Updated launchSettings.json to set anonymousAuthentication to true. Modified web.config to add <anonymousAuthentication enabled="true" /> under <authentication>, allowing both anonymous and Windows authentication. Reformatted web.config for improved readability.
2026-03-18 09:41:17 +01:00
81c6604295 Enable Windows Authentication in web.config
Added <security> section to web.config to enable Windows Authentication with NTLM and Negotiate providers under <system.webServer>. This allows the application to authenticate users using their Windows credentials.
2026-03-18 09:41:08 +01:00
893b44565c Enable Swagger via config and add project version metadata
Added versioning fields to FakeNTLMServer.csproj for assembly and informational versioning. Introduced "EnableSwagger" setting in appsettings.json to allow Swagger UI outside development environments. Reformatted authentication registration in Program.cs for clarity.
2026-03-16 15:17:57 +01:00
bcf38ee384 Refactor AuthController 'me' endpoint and remove auth
- Changed [HttpGet("me")] to [HttpGet(nameof(Me))] for route safety.
- Renamed method from GetMe to Me for consistency.
- Removed [Authorize] attribute to allow unauthenticated access.
2026-03-16 10:06:31 +01:00
3cecc0695f Add web.config for IIS hosting and development setup
Configured ASP.NET Core for IIS with AspNetCoreModuleV2, in-process hosting, and set environment to Development.
2026-03-16 09:44:53 +01:00
62dd45dd08 Restrict Kestrel server to HTTP/1 protocol in config
Added "Kestrel" section to appsettings.json to set endpoint default protocol to HTTP/1, preventing use of HTTP/2 or other protocols.
2026-03-16 09:44:41 +01:00
cfc74276ae Update AuthController routes; add Test endpoint
Refactored route attributes for Login and Status actions to use nameof() for improved maintainability. Added a new Test GET endpoint that returns a simple OK response.
2026-03-13 13:02:01 +01:00
7926d3d93f Enhance Swagger UI with Negotiate auth and XML docs
- Add custom OpenAPI doc with title, version, and description
- Define "Negotiate" security scheme for NTLM/Kerberos auth
- Require Negotiate authentication for all endpoints in Swagger
- Include XML comments in Swagger UI if available
- Configure Swagger UI to send credentials (withCredentials: true) for authenticated endpoint testing
2026-03-13 10:37:16 +01:00
5b37dbf854 Add POST /auth/login for Windows credential auth
Introduced a new endpoint to AuthController that allows authentication using Windows username, password, and optional domain via the Win32 LogonUser API. This enables credential validation without NTLM/Negotiate middleware or IIS. The endpoint parses both "DOMAIN\user" and "user@domain" formats and returns user info and claims on success, or Unauthorized on failure. Added necessary using directives for implementation.
2026-03-13 10:36:54 +01:00
ee9f4abc95 Add NtlmHelper for NTLM credential validation via LogonUser
Introduced the NtlmHelper static class in the FakeNTLMServer.Common namespace. This class provides a ValidateCredentials method that uses P/Invoke to call the Windows LogonUser API, allowing validation of NTLM credentials and returning a SafeAccessTokenHandle on success. Constants for logon type and provider are included, and token validity is checked.
2026-03-13 10:35:59 +01:00
d8c87f25d8 Add Login model with username, password, and domain fields
Introduced a Login class in the FakeNTLMServer.Model namespace to represent user credentials. The model includes required Username and Password properties, supporting various username formats, and an optional Domain property that defaults to the local machine if not specified.
2026-03-13 10:31:12 +01:00
8a8006874d Refactor AuthController and add NTLM login endpoint
Refactored AuthController to improve attribute usage and code clarity. Added three endpoints: /auth/me (user info), /auth/login (NTLM/Negotiate authentication with user info or 401), and /auth/status (authenticated user status). Responses are now more structured and informative. Applied [Authorize] only to relevant endpoints. Improved code organization and documentation.
2026-03-13 10:02:19 +01:00
7 changed files with 304 additions and 40 deletions

33
Common/NtlmHelper.cs Normal file
View File

@@ -0,0 +1,33 @@
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
namespace FakeNTLMServer.Common
{
public static class NtlmHelper
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out SafeAccessTokenHandle phToken);
private const int LOGON32_LOGON_NETWORK = 3;
private const int LOGON32_PROVIDER_DEFAULT = 0;
public static bool ValidateCredentials(string username, string domain, string password, out SafeAccessTokenHandle token)
{
var success = LogonUser(
username,
domain,
password,
LOGON32_LOGON_NETWORK,
LOGON32_PROVIDER_DEFAULT,
out token);
return success && token is not null && !token.IsInvalid;
}
}
}

View File

@@ -1,24 +1,108 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Principal;
using FakeNTLMServer.Model;
using FakeNTLMServer.Common;
namespace FakeNTLMServer.Controllers
namespace FakeNTLMServer.Controllers;
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
[ApiController]
[Route("[controller]")]
[Authorize]
public class AuthController : ControllerBase
[HttpGet(nameof(Me))]
public IActionResult Me()
{
[HttpGet("me")]
public IActionResult GetMe()
var identity = User.Identity;
return Ok(new
{
var identity = User.Identity;
identity?.Name,
identity?.AuthenticationType,
identity?.IsAuthenticated,
Claims = User.Claims.Select(claim => new { claim.Type, claim.Value })
});
}
/// <summary>
/// NTLM/Negotiate login endpoint.
/// Triggers the NTLM handshake and returns authenticated user info.
/// </summary>
[Authorize]
[HttpGet(nameof(Login))]
public IActionResult Login()
{
var identity = User.Identity;
if (identity is null || !identity.IsAuthenticated)
return Unauthorized(new { Message = "NTLM authentication failed." });
return Ok(new
{
Message = "NTLM authentication successful.",
identity.Name,
identity.AuthenticationType,
identity.IsAuthenticated,
Claims = User.Claims.Select(claim => new { claim.Type, claim.Value })
});
}
/// <summary>
/// Validates Windows credentials (username/password) using the Win32 LogonUser API.
/// Works on local Kestrel without IIS or Negotiate middleware.
/// </summary>
[AllowAnonymous]
[HttpPost("login")]
public IActionResult LoginWithCredentials([FromBody] Login request)
{
var username = request.Username;
var domain = request.Domain ?? ".";
if (username.Contains('\\'))
{
var parts = username.Split('\\', 2);
domain = parts[0];
username = parts[1];
}
else if (username.Contains('@'))
{
var parts = username.Split('@', 2);
username = parts[0];
domain = parts[1];
}
if (!NtlmHelper.ValidateCredentials(username, domain, request.Password, out var token))
{
return Unauthorized(new { Message = "Invalid username or password." });
}
using (token)
{
var windowsIdentity = new WindowsIdentity(token.DangerousGetHandle());
var claims = windowsIdentity.Claims.Select(c => new { c.Type, c.Value }).ToList();
return Ok(new
{
identity?.Name,
identity?.AuthenticationType,
identity?.IsAuthenticated,
Claims = User.Claims.Select(claim => new { claim.Type, claim.Value })
Message = "Authentication successful.",
Name = windowsIdentity.Name,
AuthenticationType = windowsIdentity.AuthenticationType,
IsAuthenticated = windowsIdentity.IsAuthenticated,
Claims = claims
});
}
}
[Authorize]
[HttpGet(nameof(Status))]
public IActionResult Status()
{
return Ok(new
{
User.Identity?.Name,
User.Identity?.AuthenticationType
});
}
[HttpGet(nameof(Test))]
public IActionResult Test() => Ok();
}

View File

@@ -1,13 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>1.0.1-beta</Version>
<AssemblyVersion>1.0.1.0</AssemblyVersion>
<FileVersion>1.0.1.0</FileVersion>
<InformationalVersion>1.0.1-beta</InformationalVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="8.0.0" />
<PackageReference Include="NLog" Version="6.1.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="6.1.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>

23
Model/Login.cs Normal file
View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace FakeNTLMServer.Model;
public class Login
{
/// <summary>
/// Username. Supports formats: "username", "DOMAIN\username", "username@domain"
/// </summary>
[Required]
public string Username { get; set; } = default!;
/// <summary>
/// Password
/// </summary>
[Required]
public string Password { get; set; } = default!;
/// <summary>
/// Domain (optional). Defaults to local machine ("."). Ignored if domain is included in Username.
/// </summary>
public string? Domain { get; set; }
}

View File

@@ -1,32 +1,88 @@
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.OpenApi.Models;
using NLog;
using NLog.Web;
var builder = WebApplication.CreateBuilder(args);
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
try
{
app.UseSwagger();
app.UseSwaggerUI();
var builder = WebApplication.CreateBuilder(args);
if (!builder.Environment.IsDevelopment())
{
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
builder.Services.AddControllers();
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "FakeNTLMServer",
Version = "v1",
Description = "NTLM/Negotiate authentication test server"
});
options.AddSecurityDefinition("Negotiate", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "Negotiate",
Description = "Windows Authentication (NTLM/Kerberos). Credentials are sent automatically by the browser."
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Negotiate"
}
},
Array.Empty<string>()
}
});
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))
options.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || app.Configuration.GetValue<bool>("EnableSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
// Enable sending credentials (NTLM tokens) with Swagger UI requests
options.ConfigObject.AdditionalItems["withCredentials"] = true;
});
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
catch (Exception ex)
{
logger.Error(ex, "Stopped program because of exception");
throw;
}

View File

@@ -5,5 +5,54 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1"
}
},
"EnableSwagger": true,
"NLog": {
"throwConfigExceptions": true,
"variables": {
"logDirectory": "E:\\LogFiles\\Digital Data\\FakeNTLMServer",
"logFileNamePrefix": "${shortdate}-FakeNTLMServer"
},
"targets": {
"infoLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
"maxArchiveDays": 30
},
"criticalLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
"maxArchiveDays": 30
}
},
// Trace, Debug, Info, Warn, Error and *Fatal*
"rules": [
{
"logger": "*",
"minLevel": "Info",
"maxLevel": "Warn",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Error",
"writeTo": "errorLogs"
},
{
"logger": "*",
"level": "Fatal",
"writeTo": "criticalLogs"
}
]
}
}

13
web.config Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" hostingModel="InProcess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
</aspNetCore>
</system.webServer>
</configuration>