@@ -1,6 +1,5 @@
using System.Security.Claims ;
using Microsoft.AspNetCore.Authentication ;
using Microsoft.AspNetCore.Authentication.Negotiate ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Components.Authorization ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.EntityFrameworkCore ;
@@ -10,164 +9,111 @@ using RIIT.Data;
var builder = WebApplication . CreateBuilder ( args ) ;
// ===== Auth mode switches (configurable via appsettings.json ) =====
// ===== Pøepínaè režimu (appsettings.json: "Auth:Mode": "Windows" | "Internal" ) =====
var authMode = builder . Configuration [ "Auth:Mode" ] ? . Trim ( ) ? ? "Internal" ;
var windowsEnabled = string . Equals ( authMode , "Windows" , StringComparison . OrdinalIgnoreCase ) ;
// Detect IIS / IIS Express hosting (intranet scenario )
var runningUnderIis = ! string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "ASPNETCORE_IIS_PHYSICAL_PATH" ) ) ;
if ( windowsEnabled )
{
// ? Negotiate v aplikaci (Kestrel) – POZOR: nepoužívej za IIS s Windows Auth!
builder . Services . AddAuthentication ( NegotiateDefaults . AuthenticationScheme )
. AddNegotiate ( ) ;
// Add services to the container.
builder . Services . AddRazorComponents ( )
. AddInteractiveServerComponents ( ) ;
builder . Services . AddCascadingAuthenticationState ( ) ;
// Vyžaduj pøihlášení všude (Fallback = Default)
builder . Services . AddAuthorization ( options = >
{
options . FallbackPolicy = options . DefaultPolicy ;
} ) ;
}
else
{
// ? Interní Identity úèty (cookies)
builder . Services . AddScoped < IdentityRedirectManager > ( ) ;
builder . Services . AddScoped < AuthenticationStateProvider , IdentityRevalidatingAuthenticationStateProvider > ( ) ;
// Authentication: primary = Identity cookie
builder . Services . AddAuthentication ( options = >
{
options . DefaultScheme = IdentityConstants . ApplicationScheme ;
options . DefaultSignInScheme = IdentityConstants . ExternalScheme ;
// Keep challenge on cookie/login page; Windows is performed explicitly via /auth/windows.
options . DefaultChallengeScheme = IdentityConstants . ApplicationScheme ;
} )
. AddIdentityCookies ( ) ;
// Windows Integrated (Kerberos/NTLM):
// - On IIS / IIS Express: do NOT register AddNegotiate(); IIS handles Windows auth and populates HttpContext.User.
// - Outside IIS: you may register AddNegotiate() (optional), but typical intranet hosting uses IIS anyway.
if ( windowsEnabled & & ! runningUnderIis )
{
builder . Services . AddAuthentication ( )
. AddNegotiate ( ) ;
}
builder . Services . AddAuthorization ( ) ;
var connectionString = builder . Configuration . GetConnectionString ( "DefaultConnection" )
? ? throw new InvalidOperationException ( "Connection string 'DefaultConnection' not found." ) ;
builder . Services . AddDbContext < ApplicationDbContext > ( options = >
options . UseSqlServer ( connectionString ) ) ;
builder . Services . AddDatabaseDeveloperPageExceptionFilter ( ) ;
builder . Services . AddIdentityCore < ApplicationUser > ( options = >
{
// Email confirmation disabled for now (can be enabled later)
options . SignIn . RequireConfirmedAccount = false ;
options . SignIn . RequireConfirmedAccount = true ;
options . Stores . SchemaVersion = IdentitySchemaVersions . Version3 ;
} )
. AddEntityFrameworkStores < ApplicationDbContext > ( )
. AddSignInManager ( )
. AddDefaultTokenProviders ( ) ;
}
// DB
var connectionString = builder . Configuration . GetConnectionString ( "DefaultConnection" )
? ? throw new InvalidOperationException ( "Connection string 'DefaultConnection' not found." ) ;
builder . Services . AddDbContext < ApplicationDbContext > ( o = > o . UseSqlServer ( connectionString ) ) ;
builder . Services . AddDatabaseDeveloperPageExceptionFilter ( ) ;
// Email (no-op)
builder . Services . AddSingleton < IEmailSender < ApplicationUser > , IdentityNoOpEmailSender > ( ) ;
// Blazor (server interaktivnì) + cascading auth state (AuthorizeView apod.)
builder . Services . AddRazorComponents ( )
. AddInteractiveServerComponents ( ) ;
builder . Services . AddCascadingAuthenticationState ( ) ;
var app = builder . Build ( ) ;
// Optionally guard against misconfiguration
// (Windows mode is intended for IIS/IIS Express intranet hosting )
if ( windowsEnabled & & ! runningUnderIis )
// ------ Pipeline ------
if ( ! app . Environment . IsDevelopment ( ) )
{
app . Logger . LogWarning ( "Auth:Mode=Windows but the app is not running under IIS/IIS Express. Windows Integrated auth may not work as expected. ") ;
}
// Configure the HTTP request pipeline.
if ( app . Environment . IsDevelopment ( ) )
{
app . UseMigrationsEndPoint ( ) ;
}
else
{
app . UseExceptionHandler ( "/Error" , createScopeForErrors : true ) ;
app . UseExceptionHandler ( "/Error ") ;
app . UseHsts ( ) ;
}
app . UseStatusCodePagesWithReExecute ( "/not-found" ) ;
app . UseHttpsRedirection ( ) ;
app . UseStaticFiles ( ) ;
app . UseRouting ( ) ;
app . UseAuthentication ( ) ;
if ( windowsEnabled )
{
app . Use ( async ( context , next ) = >
{
// Pokud už je pøihlášený (cookie), nech vše projít.
if ( context . User ? . Identity ? . IsAuthenticated = = true )
{
await next ( ) ;
return ;
}
var path = context . Request . Path ;
// Nikdy neredirectovat:
// - bootstrap endpoint
// - identity stránky/endpoints
// - chybové stránky
// - blazor framework a static web assets
if ( path . StartsWithSegments ( "/auth/windows" ) | |
path . StartsWithSegments ( "/Account" ) | |
path . StartsWithSegments ( "/not-found" ) | |
path . StartsWithSegments ( "/Error" ) | |
path . StartsWithSegments ( "/_framework" ) | |
path . StartsWithSegments ( "/_content" ) )
{
await next ( ) ;
return ;
}
// Nezasahovat do statických souborù podle pøípony (CSS/JS/fonts/images/maps)
var ext = System . IO . Path . GetExtension ( path ) ;
if ( ! string . IsNullOrEmpty ( ext ) )
{
await next ( ) ;
return ;
}
// Redirect jen pro "navigaèní" requesty (typicky HTML stránky)
if ( ! HttpMethods . IsGet ( context . Request . Method ) )
{
await next ( ) ;
return ;
}
// Pokud klient nechce HTML (napø. fetch pro JSON), neredirectuj
var accept = context . Request . Headers . Accept . ToString ( ) ;
if ( ! string . IsNullOrEmpty ( accept ) & & ! accept . Contains ( "text/html" , StringComparison . OrdinalIgnoreCase ) )
{
await next ( ) ;
return ;
}
var returnUrl = context . Request . PathBase + context . Request . Path + context . Request . QueryString ;
var target = "/auth/windows?returnUrl=" + Uri . EscapeDataString ( returnUrl ) ;
context . Response . Redirect ( target ) ;
} ) ;
}
app . UseAuthorization ( ) ;
app . UseAntiforgery ( ) ;
app . MapStaticAssets ( ) ;
app . MapRazorComponents < App > ( )
. AddInteractiveServerRenderMode ( ) ;
// Add additional endpoints required by the Identity /Account Razor components.
app . MapAdditionalIdentityEndpoints ( ) ;
if ( ! windowsEnabled )
{
app . MapAdditionalIdentityEndpoints ( ) ;
}
/*
// ? StatusCodePages: nikdy nesahat do 401/403 (kvùli Negotiate handshaku)
app.UseStatusCodePages(async context =>
{
var code = context.HttpContext.Response.StatusCode;
// Negotiate/Identity challenge – nesahej na to
if (code == 401 || code == 403) return;
// 404 apod. klidnì pøesmìruj
context.HttpContext.Response.Redirect("/not-found");
});
if (windowsEnabled)
{
// FallbackPolicy už vyžaduje auth – tady .RequireAuthorization() není nutné
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
}
else
{
// Interní úèty: výslovnì zamknout UI
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.RequireAuthorization();
}
*/
app . Run ( ) ;