Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
32 KiB
Web API and Authentication Specification
Purpose
This specification defines the REST API endpoints, SignalR real-time communication hub, and LDAP authentication system for the JDE Scoping Tool. The system provides a single .NET 10 service that exposes REST APIs for Blazor WebAssembly clients to manage searches, perform lookup operations, upload/download files, and receive real-time status updates via SignalR. Authentication is performed against an LDAP directory server with group membership verification, with support for a development-mode bypass.
Source Reference
| Legacy Files | NEW/ Target | Purpose |
|---|---|---|
| OLD/WebInterface/Controllers/SearchController.cs | NEW/ScopingTool.Api/Controllers/SearchController.cs | Search management API - create, view, copy, save searches and download results |
| OLD/WebInterface/Controllers/AccountController.cs | NEW/ScopingTool.Api/Controllers/AuthController.cs | User authentication - login, logout, authorization |
| OLD/WebInterface/Controllers/LookupController.cs | NEW/ScopingTool.Api/Controllers/LookupController.cs | Autocomplete lookup APIs for items, profit centers, work centers, operators |
| OLD/WebInterface/Controllers/FileIOController.cs | NEW/ScopingTool.Api/Controllers/FileController.cs | Excel file upload/download for bulk data import |
| OLD/WebInterface/Controllers/CrudController.cs | NEW/ScopingTool.Api/Controllers/ApiControllerBase.cs | Base controller with current user context |
| OLD/WebInterface/Hubs/StatusHub.cs | NEW/ScopingTool.Api/Hubs/StatusHub.cs | SignalR hub for real-time status and search updates |
| OLD/WebInterface/Helpers/LDAPHelper.cs | NEW/ScopingTool.Api/Services/LdapAuthService.cs | LDAP server authentication and user lookup |
| OLD/WebInterface/Security/UserIdentity.cs | NEW/ScopingTool.Api/Security/UserIdentity.cs | Claims-based user identity from LDAP |
| OLD/WebInterface/Models/LogonRequest.cs | NEW/ScopingTool.Api/Models/LoginRequest.cs | Login request model |
| OLD/DataModel/Models/LDAPEntry.cs | NEW/ScopingTool.Domain/Models/UserInfo.cs | User information model (renamed from LDAPEntry) |
| OLD/DataModel/Models/StatusUpdate.cs | NEW/ScopingTool.Domain/Models/StatusUpdate.cs | Process status update model |
| OLD/DataModel/Models/SearchUpdate.cs | NEW/ScopingTool.Domain/Models/SearchUpdate.cs | Search status update model |
Requirements
Requirement: Authentication Service Interface
The system SHALL provide an abstraction for authentication to support LDAP authentication in production and fake authentication in development mode.
Interface Definition
public interface IAuthService
{
Task<AuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default);
Task<UserInfo?> GetUserInfoAsync(string username, CancellationToken ct = default);
}
public record AuthResult(bool Success, UserInfo? User, string? ErrorMessage);
Implementations
LdapAuthService- Production LDAP authentication usingSystem.DirectoryServices.ProtocolsFakeAuthService- Development mode bypass that accepts any credentials
Business Rules
- The system SHALL register
IAuthServicein the DI container based on configuration - The system SHALL use
LdapAuthServicewhenAuthOptions.UseFakeAuthis false - The system SHALL use
FakeAuthServicewhenAuthOptions.UseFakeAuthis true (development only) FakeAuthServiceSHALL return a predefinedUserInfofor any username/password combination
Scenario: Production mode uses LDAP authentication
- WHEN the application starts with
AuthOptions.UseFakeAuth = false - THEN
LdapAuthServiceis registered asIAuthServiceand all authentication flows use LDAP
Scenario: Development mode uses fake authentication
- WHEN the application starts with
AuthOptions.UseFakeAuth = true - THEN
FakeAuthServiceis registered asIAuthServiceand any credentials are accepted
Requirement: Configuration Options
The system SHALL use strongly-typed configuration options for LDAP and authentication settings.
LdapOptions
public class LdapOptions
{
public const string SectionName = "Ldap";
public string[] ServerUrls { get; set; } = Array.Empty<string>();
public string GroupDn { get; set; } = string.Empty;
public string SearchBase { get; set; } = string.Empty;
public int ConnectionTimeoutSeconds { get; set; } = 30;
}
AuthOptions
public class AuthOptions
{
public const string SectionName = "Auth";
public bool UseFakeAuth { get; set; } = false;
public string CookieName { get; set; } = "ScopingTool.Auth";
public int CookieExpirationMinutes { get; set; } = 480; // 8 hours
public string[] AdminBypassUsers { get; set; } = Array.Empty<string>(); // Usernames that bypass group check
public long MaxUploadSizeBytes { get; set; } = 10 * 1024 * 1024; // 10MB default
}
LDAP Connection Management
The system SHALL use a connection-per-request pattern for LDAP connections. Each authentication request creates a new LdapConnection, authenticates, and disposes. Connection pooling is NOT used due to LDAP bind credential requirements.
Business Rules
- The system SHALL bind
LdapOptionsfrom theLdapconfiguration section - The system SHALL bind
AuthOptionsfrom theAuthconfiguration section - The system SHALL use
IOptions<LdapOptions>andIOptions<AuthOptions>for injection
Requirement: LDAP Authentication
The system SHALL authenticate users against an LDAP directory server and verify group membership before granting access.
Inputs
- Username (sAMAccountName format)
- Password (plain text, transmitted over HTTPS)
- LDAP server URLs (from
IOptions<LdapOptions>, supports multiple URLs for failover) - LDAP group distinguished name (from
IOptions<LdapOptions>)
Outputs
AuthResultcontaining:- Success/failure indicator
UserInfoon success (see Data Models section)- Error message on failure
Business Rules
- The system SHALL use
System.DirectoryServices.Protocols.LdapConnectionfor cross-platform compatibility - The system SHALL attempt authentication against each configured LDAP server URL sequentially until one succeeds
- The system SHALL verify the user is a member of the configured LDAP group after successful bind
- The system SHALL use the sAMAccountName LDAP filter format
(sAMAccountName={0})for user lookup - The system SHALL extract user properties: distinguishedName, givenName, sn, mail, title
- The system SHALL compute DisplayName as
"{FirstName} {LastName}".Trim()or fall back to Username if both are empty - The system SHALL sign out any existing session before creating a new one
- The system SHALL use non-persistent (session-only) authentication cookies
Scenario: Successful LDAP authentication with group membership
- WHEN a user submits valid credentials for an LDAP user who is a member of the required group
- THEN the system authenticates against the LDAP server, verifies group membership, creates a claims-based identity, signs in the user, and returns a success response with user info
Scenario: Failed LDAP authentication with invalid credentials
- WHEN a user submits invalid credentials
- THEN the system returns
AuthResult { Success = false, ErrorMessage = "Incorrect username or password" }
Scenario: Valid credentials but user not in required group
- WHEN a user submits valid credentials but is not a member of the required LDAP group
- THEN the system returns
AuthResult { Success = false, ErrorMessage = "User is not a member of the required security group" }
Scenario: LDAP server unavailable with failover
- WHEN the primary LDAP server is unavailable but a secondary server is configured
- THEN the system attempts authentication against each configured server in sequence until one succeeds or all fail
Scenario: All LDAP servers unavailable
- WHEN all configured LDAP servers are unavailable
- THEN the system returns
AuthResult { Success = false, ErrorMessage = "Unable to connect to directory server" }
Requirement: User Session Management
The system SHALL maintain user session state using ASP.NET Core cookie authentication.
Inputs
- Authenticated user identity (from LDAP or fake auth)
- Session cookie
Outputs
- User context available to all authorized controllers via
HttpContext.User - Session termination on logout
Business Rules
- The system SHALL use
HttpContext.SignInAsync()withCookieAuthenticationDefaults.AuthenticationSchemefor sign-in - The system SHALL use
HttpContext.SignOutAsync()for sign-out - The system SHALL store user claims in a
ClaimsPrincipalwith cookie authentication scheme - The system SHALL provide access to current user's
UserInfothroughHttpContext.Userclaims - The system SHALL clear authentication cookies on logout
- The system SHALL return HTTP 401 Unauthorized for unauthenticated API requests (no redirect for Blazor WASM)
Scenario: Access protected resource while authenticated
- WHEN an authenticated user requests a protected resource
- THEN the system provides the current user context and serves the requested resource
Scenario: Access protected resource while unauthenticated
- WHEN an unauthenticated user requests a protected resource with
[Authorize]attribute - THEN the system returns HTTP 401 Unauthorized
Scenario: User logs out
- WHEN an authenticated user requests logout via
POST /api/auth/logout - THEN the system calls
HttpContext.SignOutAsync(), clears all authentication cookies, and returns HTTP 200 OK
Scenario: Parse user identity from claims
- WHEN a controller accesses
HttpContext.User - THEN the system provides access to user claims including Username, FirstName, LastName, Email, and Title
Requirement: Auth API Endpoints
The system SHALL provide REST API endpoints for authentication operations.
Inputs
| Endpoint | Method | Parameters | Description |
|---|---|---|---|
/api/auth/login |
POST | LoginRequest body |
Authenticate user |
/api/auth/logout |
POST | - | Sign out current user |
/api/auth/me |
GET | - | Get current user info |
Outputs
- JSON responses with
UserInfoon success - HTTP 401 Unauthorized on authentication failure
- HTTP 200 OK on successful logout
Business Rules
- The system SHALL use
[ApiController]attribute on the controller - The system SHALL use attribute routing with
/api/authprefix - The system SHALL return
UserInfoJSON (not redirect) on successful login for Blazor WASM compatibility - The system SHALL call
IAuthService.AuthenticateAsync()for login operations
Scenario: Successful login
- WHEN a user posts valid credentials to
/api/auth/login - THEN the system authenticates, creates a session, and returns HTTP 200 with
UserInfoJSON
Scenario: Failed login
- WHEN a user posts invalid credentials to
/api/auth/login - THEN the system returns HTTP 401 Unauthorized with error message
Scenario: Get current user
- WHEN an authenticated user requests
/api/auth/me - THEN the system returns HTTP 200 with
UserInfoJSON for the current user
Scenario: Get current user when not authenticated
- WHEN an unauthenticated user requests
/api/auth/me - THEN the system returns HTTP 401 Unauthorized
Requirement: Search API Endpoints
The system SHALL provide REST API endpoints for search management operations.
Inputs
| Endpoint | Method | Parameters | Description |
|---|---|---|---|
/api/search |
GET | - | Get current user's searches |
/api/search/queue |
GET | - | Get all queued searches |
/api/search/{id} |
GET | id (int) | Get search by ID |
/api/search/{id}/copy |
POST | id (int) | Copy search with reset status |
/api/search |
POST | SearchViewModel body |
Create/submit search |
/api/search/{id}/results |
GET | id (int) | Download search results Excel file |
Outputs
- JSON responses using System.Text.Json serialization
- File download for results (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)
- Search ID on successful create
Business Rules
- The system SHALL use
[ApiController]attribute on the controller - The system SHALL use attribute routing with
/api/searchprefix - The system SHALL require authorization for all search endpoints via
[Authorize]attribute - The system SHALL filter GetSearches to only return searches owned by the current user
- The system SHALL order user searches by StartDT descending (most recent first)
- The system SHALL reset Status, UserName, SubmitDT, StartDT, and EndDT when copying a search
- The system SHALL publish a SearchUpdate to the SignalR hub when a new search is saved
- The system SHALL return the new search ID on successful create
- The system SHALL set filename to "search_results.xlsx" for result downloads
- The system SHALL inject
IHubContext<StatusHub>via DI for SignalR notifications
Scenario: Get user's search history
- WHEN an authenticated user requests
GET /api/search - THEN the system returns a JSON array of
SearchViewModelobjects for searches owned by that user, ordered by most recent first
Scenario: Create new search
- WHEN an authenticated user posts a
SearchViewModeltoPOST /api/search - THEN the system converts the view model to a Search entity, submits it to the database, publishes a SignalR notification via
IHubContext<StatusHub>, and returns HTTP 201 Created with the new search ID
Scenario: Copy existing search
- WHEN an authenticated user requests
POST /api/search/{id}/copy - THEN the system loads the original search, resets the status to New, clears timestamps, sets the current user as owner, saves the copy, and returns HTTP 201 Created with the new search ID
Scenario: Download search results
- WHEN an authenticated user requests
GET /api/search/{id}/results - THEN the system retrieves the binary Excel data from the database and returns it as a file download with Content-Type
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Scenario: Download results for search without results
- WHEN an authenticated user requests results for a search that has not completed
- THEN the system returns HTTP 404 Not Found
Requirement: Lookup API Endpoints
The system SHALL provide REST API endpoints for autocomplete/lookup operations.
Inputs
| Endpoint | Method | Parameters | Description |
|---|---|---|---|
/api/lookup/items |
GET | q (string) |
Search items by number |
/api/lookup/profit-centers |
GET | q (string) |
Search profit centers |
/api/lookup/work-centers |
GET | q (string) |
Search work centers |
/api/lookup/operators |
GET | q (string) |
Search operators/users |
Outputs
- JSON arrays of matching entities converted to view models
- Results ordered alphabetically by primary identifier
Business Rules
- The system SHALL use
[ApiController]attribute on the controller - The system SHALL use attribute routing with
/api/lookupprefix - The system SHALL NOT require authorization for lookup endpoints (public access)
- The system SHALL order item results by ItemNumber
- The system SHALL order profit center results by Code
- The system SHALL order work center results by Code
- The system SHALL order operator results by FullName
Scenario: Search for items by partial number
- WHEN a user requests
GET /api/lookup/items?q=ABC - THEN the system searches for items matching the partial number and returns matching
ItemViewModelobjects ordered by ItemNumber
Scenario: Search for profit centers
- WHEN a user requests
GET /api/lookup/profit-centers?q=100 - THEN the system searches for profit centers matching the partial code and returns matching view models ordered by Code
Scenario: Search for work centers
- WHEN a user requests
GET /api/lookup/work-centers?q=MACH - THEN the system searches for work centers matching the partial code and returns matching view models ordered by Code
Scenario: Search for operators by name
- WHEN a user requests
GET /api/lookup/operators?q=Smith - THEN the system searches for users matching the partial name and returns matching view models ordered by FullName
Requirement: File API Endpoints
The system SHALL provide REST API endpoints for Excel file upload and download operations using ClosedXML.
Inputs
| Endpoint | Method | Parameters | Description |
|---|---|---|---|
/api/file/work-orders/upload |
POST | IFormFile file |
Upload work orders from Excel |
/api/file/work-orders/template |
POST | List<long> body |
Generate work order template, returns cache key |
/api/file/work-orders/template/{key} |
GET | key (Guid) | Download cached template |
/api/file/part-numbers/upload |
POST | IFormFile file |
Upload part numbers from Excel |
/api/file/part-numbers/template |
POST | List<ItemViewModel> body |
Generate part number template |
/api/file/part-numbers/template/{key} |
GET | key (Guid) | Download cached template |
/api/file/component-lots/upload |
POST | IFormFile file |
Upload component lots from Excel |
/api/file/component-lots/template |
POST | List<LotViewModel> body |
Generate component lot template |
/api/file/component-lots/template/{key} |
GET | key (Guid) | Download cached template |
/api/file/part-operations/upload |
POST | IFormFile file |
Upload part operations from Excel |
/api/file/part-operations/template |
POST | List<PartOperationViewModel> body |
Generate part operations template |
/api/file/part-operations/template/{key} |
GET | key (Guid) | Download cached template |
Outputs
FileUploadResult<T>with WasSuccessful, ErrorMessage, and Data properties- GUID key for cached file downloads
- Excel file stream for GET download requests
Business Rules
- The system SHALL use
[ApiController]attribute on the controller - The system SHALL use attribute routing with
/api/fileprefix - The system SHALL NOT require authorization for file endpoints (matches legacy behavior)
- The system SHALL accept file uploads via
IFormFileparameter - The system SHALL parse Excel files using ClosedXML library, reading from row 2 (skip header)
- The system SHALL inject
IMemoryCachevia DI for template caching - The system SHALL cache generated templates with 1-minute absolute expiration
- The system SHALL use GUID keys for cached file retrieval
- The system SHALL return HTTP 404 if cached file not found or expired
- The system SHALL remove cached data after successful download
- The system SHALL deduplicate uploaded data using DistinctBy before returning (except part operations)
Scenario: Upload work orders from Excel
- WHEN a user uploads an Excel file to
POST /api/file/work-orders/upload - THEN the system parses work order numbers from column 1 using ClosedXML, looks up matching work orders in the database, deduplicates results, and returns a
FileUploadResultwith matchingWorkOrderViewModelobjects
Scenario: Generate and download work order template
- WHEN a user posts work order numbers to
POST /api/file/work-orders/template - THEN the system generates an Excel template using ClosedXML, caches it with a GUID key, and returns the key
- WHEN the user requests
GET /api/file/work-orders/template/{guid} - THEN the system retrieves the cached file and returns it as "work_order_template.xlsx"
Scenario: Upload component lots with item numbers
- WHEN a user uploads an Excel file with lot numbers and item numbers to
POST /api/file/component-lots/upload - THEN the system parses lot numbers from column 1 and item numbers from column 2, looks up matching lots, deduplicates, and returns matching
LotViewModelobjects
Scenario: Cached file expired
- WHEN a user requests a download with an expired or invalid cache key
- THEN the system returns HTTP 404 Not Found
Scenario: No file uploaded
- WHEN a user posts to an upload endpoint without a file
- THEN the system returns
FileUploadResult { WasSuccessful = false, ErrorMessage = "No file uploaded" }
Requirement: SignalR Real-Time Updates
The system SHALL provide real-time status updates to connected clients via ASP.NET Core SignalR hub.
Inputs
- Status updates from worker service (via
IHubContext<StatusHub>) - Search status changes (via
IHubContext<StatusHub>) - Client requests for cached status (GetCachedStatus hub method)
Outputs
- Broadcasts to all connected clients via
SendAsync("statusUpdate", ...)andSendAsync("searchUpdate", ...)
Business Rules
- The system SHALL use ASP.NET Core SignalR (
Microsoft.AspNetCore.SignalR) - The system SHALL maintain a cached
StatusUpdatewith message and timestamp - The system SHALL broadcast status updates to ALL connected clients using
Clients.All.SendAsync() - The system SHALL inject
IHubContext<StatusHub>into controllers and services that need to publish updates - The system SHALL NOT use static
GlobalHostpattern - use DI exclusively - The system SHALL initialize cached status with "Unknown" message and current timestamp
- The system SHALL map the hub endpoint to
/hubs/status
Scenario: Worker service publishes status update
- WHEN the worker service publishes a
StatusUpdateviaIHubContext<StatusHub> - THEN the system caches the update and broadcasts it to all connected clients via
Clients.All.SendAsync("statusUpdate", statusUpdate)
Scenario: New search submitted via controller
- WHEN the SearchController saves a new search
- THEN the controller uses injected
IHubContext<StatusHub>to callClients.All.SendAsync("searchUpdate", searchUpdate)
Scenario: Client requests current status
- WHEN a newly connected client invokes the
GetCachedStatushub method - THEN the system returns the most recent cached
StatusUpdate(or the default "Unknown" status if none set)
Scenario: Multiple clients receive broadcast
- WHEN a status update is broadcast
- THEN all connected SignalR clients receive the update simultaneously via their
statusUpdateorsearchUpdateevent handlers
Requirement: Blazor Client Integration
The system SHALL support Blazor WebAssembly client authentication and real-time updates.
Authentication Flow
- Blazor client calls
POST /api/auth/loginwith credentials - Server validates credentials, creates cookie-based session
- Server returns
UserInfoJSON - Client stores user info in memory and sets authenticated state
- Subsequent API calls include auth cookie automatically
SignalR Connection
- Blazor client creates
HubConnectionto/hubs/status - Client registers handlers for
statusUpdateandsearchUpdateevents - Client calls
GetCachedStatus()on connection to get initial state - Client receives real-time updates via registered handlers
Business Rules
- The system SHALL use cookie authentication (not JWT) for browser-based Blazor WASM
- The system SHALL configure CORS for same-origin only by default; cross-origin support is NOT required for single-domain deployment
- The system SHALL return JSON responses (not redirects) for all API errors
- The system SHALL support reconnection for SignalR clients with automatic retry
- The SignalR hub SHALL remain open without requiring per-request authentication (cookies validated on connection)
- The Blazor client SHALL handle SPA navigation behaviors (return URL handling, 401 interception for redirect to login)
Requirement: Authorization Patterns
The system SHALL enforce authorization using ASP.NET Core attribute-based access control.
Inputs
- Authorization attributes on controllers and actions
- User authentication status from cookie
Outputs
- Access granted or denied
- HTTP 401 Unauthorized for unauthenticated API requests
- HTTP 403 Forbidden for authenticated but unauthorized requests
Business Rules
- The system SHALL apply
[Authorize]at controller level forSearchController - The system SHALL apply
[AllowAnonymous]forAuthController.Loginaction - The system SHALL NOT require authorization for
LookupControllerorFileControllerendpoints - The system SHALL return HTTP 401 (not redirect) for unauthorized API requests to support Blazor WASM
- The system SHALL configure cookie authentication to suppress redirect on 401
Scenario: Authorized user accesses protected controller
- WHEN an authenticated user accesses a controller with
[Authorize]attribute - THEN the system allows the request to proceed to the action method
Scenario: Anonymous user accesses protected API
- WHEN an anonymous user accesses a controller with
[Authorize]attribute - THEN the system returns HTTP 401 Unauthorized (no redirect)
Scenario: User accesses public lookup endpoint
- WHEN any user (authenticated or not) accesses a
LookupControllerendpoint - THEN the system allows the request without authentication check
Data Models
UserInfo (formerly LDAPEntry)
public class UserInfo
{
public string DN { get; set; } = string.Empty; // Distinguished name
public string Username { get; set; } = string.Empty; // sAMAccountName (lowercase)
public string FirstName { get; set; } = string.Empty; // givenName
public string LastName { get; set; } = string.Empty; // sn
public string DisplayName => string.IsNullOrWhiteSpace(FirstName) && string.IsNullOrWhiteSpace(LastName)
? Username
: $"{FirstName} {LastName}".Trim(); // Computed
public string Title { get; set; } = string.Empty; // title
public string EmailAddress { get; set; } = string.Empty; // mail
}
StatusUpdate
public class StatusUpdate
{
public string Message { get; set; } = string.Empty; // Update message to display
public DateTime Timestamp { get; set; } // When message was sent
}
SearchUpdate
public class SearchUpdate
{
public int ID { get; set; } // Search primary key
public string UserName { get; set; } = string.Empty; // Username who submitted
public string Name { get; set; } = string.Empty; // Search name
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchStatus Status { get; set; } // Enum: New, Submitted, Started, Ended, Error
public DateTime? SubmitDT { get; set; } // When submitted (required for UI grid)
public DateTime? StartDT { get; set; } // When processing started (required for UI grid)
public DateTime? EndDT { get; set; } // When processing ended (required for UI grid)
public DateTime Timestamp { get; set; } // When update was generated (required for ordering)
}
FileUploadResult
public class FileUploadResult<T>
{
public bool WasSuccessful { get; set; }
public string? ErrorMessage { get; set; }
public T[]? Data { get; set; }
}
LoginRequest
public class LoginRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
AuthResult
public record AuthResult(bool Success, UserInfo? User, string? ErrorMessage);
Migration Notes
| Legacy Pattern | New Pattern | Rationale |
|---|---|---|
| ASP.NET MVC 5 Controllers | ASP.NET Core Controllers with [ApiController] |
Modern framework, automatic model binding and validation |
JsonNetResult custom result |
Results.Ok(data) or return Ok(data) |
Built-in JSON support with System.Text.Json |
Newtonsoft.Json [JsonConverter(typeof(StringEnumConverter))] |
System.Text.Json [JsonConverter(typeof(JsonStringEnumConverter))] |
Built-in serialization |
Legacy SignalR (Microsoft.AspNet.SignalR) |
ASP.NET Core SignalR (Microsoft.AspNetCore.SignalR) |
Built-in, cross-platform |
GlobalHost.ConnectionManager.GetHubContext<T>() |
IHubContext<T> via dependency injection |
Standard DI pattern, testable |
Clients.All.statusUpdate(...) |
Clients.All.SendAsync("statusUpdate", ...) |
Async-first API |
| OWIN Authentication middleware | ASP.NET Core Authentication with CookieAuthenticationDefaults |
Modern authentication stack |
HttpContext.GetOwinContext().Authentication.SignIn() |
HttpContext.SignInAsync() |
No OWIN abstraction layer |
DefaultAuthenticationTypes.ApplicationCookie |
CookieAuthenticationDefaults.AuthenticationScheme |
Standard scheme name |
System.DirectoryServices.DirectoryEntry |
System.DirectoryServices.Protocols.LdapConnection |
Cross-platform LDAP support |
WebConfigurationManager.AppSettings |
IOptions<LdapOptions> / IOptions<AuthOptions> |
Strongly-typed configuration |
MemoryCache.Default |
IMemoryCache via dependency injection |
Standard caching abstraction |
Request.Files collection |
IFormFile parameter |
Modern file upload handling |
| EPPlus library | ClosedXML library | MIT license (EPPlus changed to non-commercial) |
Route-based MVC URLs (/Search/GetSearches) |
Attribute routing (/api/search) |
REST conventions, clear API structure |
| Login redirect for unauthorized | HTTP 401 response | Blazor WASM SPA compatibility |
| NLog for logging | ILogger<T> injected + BeginScope() for context |
Built-in logging abstraction |
LDAPEntry model |
UserInfo model |
Clearer naming, not tied to LDAP implementation |
| Hardcoded user exception in code | Configurable via AuthOptions.AdminBypassUsers |
Production-safe configuration; empty array by default |
Codex Review Findings (Status)
The following issues were identified during review and their resolution status:
| Finding | Status | Resolution |
|---|---|---|
| Missing Account Endpoints | Resolved | New /api/auth/* endpoints documented |
Controller Name Typo (SessionController vs SessionsController) |
Resolved | Legacy session endpoints not needed - Blazor WASM handles client-side routing |
| InvalidUA Route | Resolved | Not needed - Blazor WASM handles user agent detection client-side |
| LDAP DisplayName Fallback | Clarified | Spec now correctly documents computed DisplayName logic |
| File Upload Error Handling | Documented | Per-row parse errors swallowed, specific error messages documented |
| SignalR Best-Effort | Documented | SignalR publish is best-effort, exceptions logged but swallowed |
Open Questions (Resolved)
| Question | Decision | Rationale |
|---|---|---|
| LDAP Failover Strategy | Keep simple sequential | Simple sequential failover is sufficient for this use case. Health checks and circuit breaker add complexity without significant benefit given the expected load. |
| Authorization Granularity | Group membership only | The legacy system only requires group membership. More granular RBAC is not needed for this tool. |
| SignalR Connection Management | Broadcast to all | Broadcasting to all clients is acceptable since the status updates are not user-specific and the number of concurrent users is expected to be small. |
| API Authentication | Cookie-based only | JWT tokens add complexity without benefit for this internal browser-based tool. Cookie auth is simpler and more secure for browser clients. |
| File Upload Security | Keep anonymous | Maintain legacy behavior. File uploads only parse Excel data; they don't access protected resources. |
| Rate Limiting | Not implemented initially | Can be added later if needed. Current user base is small and internal. |
| Hardcoded User Exception | Configurable admin bypass | Move to AuthOptions.AdminBypassUsers configuration array (empty by default). Allows dev flexibility without hardcoding. |