Search code examples
c#razorblazorblazor-server-side

User.Identity.Name is returning null even after being authorized through identityserver


When attempting to retrieve the name of the user who is currently logged in after being authorized, the User.Identity.Name is null. I do not seem to understand why it is null if I am successfully logged in from IdentityServer it redirects me back to my blazor project.

In my blazor project I have the following code set up as so:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();


            services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri("http://localhost:36626") }); // WebApi project

            services.AddTransient<IWeatherForecastServices, WeatherForecastServices>();

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)

                .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
                {
                    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.SignOutScheme = OpenIdConnectDefaults.AuthenticationScheme;

                    options.Authority = "https://localhost:5443"; // IdentityServer Project
                    options.ClientId = "interactive";
                    options.ClientSecret = "KEY";

                    options.ResponseType = "code";
                  
                    options.Scope.Add("profile"); // default scope
                    options.Scope.Add("scope2");
                    options.Scope.Add("roles");
                    options.Scope.Add("permissions");
                    options.ClaimActions.MapUniqueJsonKey("role", "role");
                    options.SaveTokens = true;
                    options.GetClaimsFromUserInfoEndpoint = true;
                   
                });
            services.AddScoped<TokenProvider>();

            services.AddCors(options =>
            {
                options.AddPolicy("Open", builder => builder.AllowAnyOrigin().AllowAnyHeader());
            }
            );
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // 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.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseCors("Open");

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }

Index.razor here variable "Name" is null:

@page "/"
@inject AuthenticationStateProvider GetAuthenticationStateAsync

<AuthorizeView>
    <Authorized>
        <h3>Welcome, <b>@Name</b></h3>
    </Authorized>
    <NotAuthorized>  
        <h3>You are signed out!!</h3>
    </NotAuthorized>
</AuthorizeView>

@code{

    private string Name;

    protected override async Task OnInitializedAsync()
    {
        var authstate = await GetAuthenticationStateAsync.GetAuthenticationStateAsync();
        var user = authstate.User;
        var name = user.Identity.Name;
        Name = name;
    }
}

My Login.cshtml.cs:

    public class LoginModel : PageModel
    {
        public async Task<IActionResult> OnGetAsync(string redirectUri)
        {
            // just to remove compiler warning
            await Task.CompletedTask;

            if (string.IsNullOrWhiteSpace(redirectUri))
            {
                redirectUri = Url.Content("~/");
            }
            // If user is already logged in, we can redirect directly...
            if (HttpContext.User.Identity.IsAuthenticated)
            {
                Response.Redirect(redirectUri);
            }

            return Challenge(
                new AuthenticationProperties
                {
                    RedirectUri = redirectUri
                },
                OpenIdConnectDefaults.AuthenticationScheme);
        }
   }

My logout.cshtml.cs:

    public class LogoutModel : PageModel
    {
     
        public async Task<IActionResult> OnGetAsync() // THIS METHOD DOES NOT DELETE THE AUTH COOKIES NOR REDIRECT YOU BACK TO BLAZOR APP EVEN THOUGH YOU HAVE THE REDIRECT URI OF BLAZOR
        {
            // just to remove compiler warning
            await Task.CompletedTask;
            var signOut = SignOut(
            new AuthenticationProperties
            {

                RedirectUri = "https://localhost:5445" // Blazor Project
            },
            OpenIdConnectDefaults.AuthenticationScheme,
            CookieAuthenticationDefaults.AuthenticationScheme);

            return signOut;
        }

    }

Frontend views:

From the start of the projects running :

enter image description here

I click Login, which will redirect me to the identityserver project:

enter image description here

Homepage view, here is where it is supposed to show me the current user logged in, but it does not:

enter image description here

When I hit logout it redirects me to identityserver saying I am logged out:

enter image description here

Is there something that is causing the variable Name to be null inside of my Index.razor file?


Solution

  • Why do you use in your Index component ?

    If you use the <AuthorizeView>, you don't have to create an AuthenticationStateProvider object... you can simply do: @context.User.Identity.Name, as the CascadingAuthenticationComponent, used in the App component, cascades the AuthenticationState object to children. If you do not have a CascadingAuthenticationComponent in your App component add it.

    It is very likely that no authentication state object is created... This imply that both the CascadingAuthenticationComponent and the AuthenticationStateProvider object return null values.

    Try the following:

    Comment out the following:

    options.Scope.Add("profile"); // default scope
    options.Scope.Add("scope2");
    options.Scope.Add("roles");
    options.Scope.Add("permissions");
    options.ClaimActions.MapUniqueJsonKey("role", "role");
    

    And use the following instead:

    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("email");
    options.TokenValidationParameters = new TokenValidationParameters
    {
         NameClaimType = "name"
    };
    
     options.UseTokenLifetime = false;
    

    You says that you've created an IdentityServer, right? If so, in your Config file, what value did you give to the ClientId property of the Client object? This settings in the ConfigureServices method: options.ClientId = "interactive"; should reflect it.

    Here's a code sample for a Client object in the Config.cs file in your IdentityServer4 project:

    public static IEnumerable<Client> Clients =>
            new Client[]
            {
                new Client
                {
                    ClientId = "blazor",
                    AllowedGrantTypes = GrantTypes.Code,
                    RequirePkce = true,
                    RequireClientSecret = false,
                    AllowedCorsOrigins = { "https://localhost:5001" },
                    AllowedScopes = { "openid", "profile", "email", "weatherapi" },
                    // where to redirect to after login
                    RedirectUris = { "https://localhost:5001/authentication/login-callback" },
                    PostLogoutRedirectUris = { "https://localhost:5001/" },
                    Enabled = true,
                    AllowOfflineAccess = true
                },
            };