Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

Blazor Authentication Issue: Injected Service Has Empty State

$
0
0

I am writing my first Blazor server-side app (.NET 8) and I am struggling with the authentication logic: when injected into a service, the user/auth information is empty.

I have a simple service to hold the user info once logged in:

public class UserService{    private string _username = string.Empty;    public string Username => _username;    public bool IsAuthenticated => !string.IsNullOrEmpty(_username);    public void SetUser(string username)    {        _username = username;    }    public void ClearUser()    {        _username = "";    }}

When it gets injected into a service, Username is always empty:

public class TestService{    private string Username { get; set; }    public TestService(UserService userService)    {        Username = userService.Username; <-- Username is empty here.    }}

Below is the rest of the related source.

Login (razor) page (Login.cshtml.cs):

    public class LoginModel : PageModel    {        private readonly LoginService _loginService;        private readonly CustomAuthenticationStateProvider _authenticationStateProvider;        [BindProperty]        public string Username { get; set; }        [BindProperty]        public string Password { get; set; }        public string ErrorMessage { get; set; }        public string ReturnUrl { get; set; } = "/";        public LoginModel(LoginService loginService, CustomAuthenticationStateProvider authenticationStateProvider)        {            _loginService = loginService;            _authenticationStateProvider = authenticationStateProvider;        }        public void OnGet(string? returnUrl = "/")        {            ReturnUrl = returnUrl ?? "/";        }        public async Task<IActionResult> OnPostAsync(string returnUrl = "/")        {            if (_loginService.CheckCredentials(Username, Password))            {                var claims = new List<Claim>                {                    new Claim(ClaimTypes.Name, Username)                };                var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);                var principal = new ClaimsPrincipal(identity);                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);                _authenticationStateProvider.NotifyUserAuthentication(Username); <-- Username is set here.                if (Url.IsLocalUrl(returnUrl))                    return Redirect(returnUrl);                return Redirect("/");            }            else            {                ErrorMessage = "Invalid username or password";                return Page();            }        }    }

Custom authentication state provider:

public class CustomAuthenticationStateProvider : AuthenticationStateProvider{    private readonly UserService _userService;    private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());    public CustomAuthenticationStateProvider(UserService userService)    {        _userService = userService;    }    public override Task<AuthenticationState> GetAuthenticationStateAsync()    {        if (!_userService.IsAuthenticated)        {            return Task.FromResult(new AuthenticationState(_anonymous));        }        var claims = new[] { new Claim(ClaimTypes.Name, _userService.Username) };  <-- _userService.Username is set here.        var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);        var user = new ClaimsPrincipal(identity);        return Task.FromResult(new AuthenticationState(user));     }    public void NotifyUserAuthentication(string username)    {        _userService.SetUser(username); <-- username is set here.        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());    }    public void NotifyUserLogout()    {        _userService.ClearUser();        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());    }}

Program.cs (a bit trimmed down):

var builder = WebApplication.CreateBuilder(args);builder.Services.AddRazorComponents()    .AddInteractiveServerComponents();builder.Services.AddDevExpressBlazor(options => {    options.BootstrapVersion = DevExpress.Blazor.BootstrapVersion.v5;    options.SizeMode = DevExpress.Blazor.SizeMode.Medium;});builder.Services.AddMvc();builder.Services.AddRazorPages();builder.Services.AddScoped<UserService>();builder.Services.AddScoped<LoginService>();builder.Services.AddScoped<TestService>();builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();builder.Services.AddBlazoredSessionStorage();builder.Services.AddDistributedMemoryCache();builder.Services.AddSession(options =>{    options.IdleTimeout = TimeSpan.FromMinutes(30);    options.Cookie.HttpOnly = true;    options.Cookie.IsEssential = true;});builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)    .AddCookie(options =>    {        options.LoginPath = "/Login"; //         options.ExpireTimeSpan = TimeSpan.FromMinutes(30);         options.SlidingExpiration = true;         options.Cookie.IsEssential = true;    });builder.Services.AddScoped<CustomAuthenticationStateProvider>();builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());builder.Services.AddAuthorizationCore();var app = builder.Build();app.UseSession();app.UseHttpsRedirection();app.UseStaticFiles(); ;app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseAntiforgery();app.MapControllers();app.MapRazorPages();app.MapRazorComponents<App>().AddInteractiveServerRenderMode();app.Run();

When the user logs in, the username is passed properly to NotifyUserAuthentication in CustomAuthenticationStateProvider (which calls _userService.SetUser) and in GetAuthenticationStateAsync.

It is only in the Constructor of the service that the username is empty.

I am using VS2022 and the app will also run under IIS in production later.

What am I missing?

Update 10/15/2024:I have uploaded the minimal reproducible example on Google Drive. Feel free to download it to reproduce the issue:Runnable MRE


Viewing all articles
Browse latest Browse all 4839

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>