0
votes

I am writing an SPA using Blazor Wasm. I have used the standard template and included user accounts hosted in the server which has created a server app as well. This is all fine so far. I would add that I am using .Net5 RC2 but I don't think that is my issue here.

I want to have some 'normal' razor pages in the server as well as those in the client app. The user accounts Identity server created the folder structure /Areas/Identity/Pages/.... I have added /Areas/Management/Pages/Admin/Test.cshtml and Test.cshtml.cs These are very simple test files...

EDIT - I have edited this to reflect the questions asked by @enet.

The razor file:

        @page
        @model ProjName.Server.Areas.Management.Pages.Admin.TestModel

        <h1>Test Page</h1>

    @if (User.Identity.IsAuthenticated)
    {
        @if (User.IsInRole("Administrator"))
        {
            <h2>User is Admin</h2>
        }
        else
        {
            <h2>User is not an admin</h2>
        }
    }
    else
    {
        <h2>User is Not Authenticated</h2>
    }

        @{
        }

The .CS file:

        namespace ProjName.Server.Areas.Management.Pages.Admin
        {
            [Authorize]    <<<--- See case B.
            public class TestModel : PageModel
            {
                public void OnGet()
                {
                }
            }
        }

I want to see the page say either User is admin, or User is not admin. In Case A : If the [Authorize] is removed, the page will load, but will always show that the user is Not authorised. So the page is being rendered and the simple test yields the 'else' case.. In case B : the page will not render at all. (This page isn't working! - message from the browser). So, from my research, in this: Razor Pages Authorization Conventions

I changed my startup.cs from this:

           services.AddRazorPages();

to this:

            services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeAreaFolder("Management", "/Admin");

        });

*** I have taken out the above and reset it to how it was ***

When I do that, the same result as case B ensues, with or without the [Authorize] in the .cs file. Which makes sense I guess when you read the docs.

So I guess I need to be passing back some form of Authorization token back, or ?

The Identity pages don't require any authorization so it isn't an issue there. My configure services looks like this:

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddDbContext<RGDbContext>(options =>
                    options.UseSqlServer(
                        Configuration.GetConnectionString("DefaultConnection")));

                services.AddDatabaseDeveloperPageExceptionFilter();

                services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                    .AddRoles<IdentityRole>()
                    .AddEntityFrameworkStores<RGDbContext>();

                // This was put in to try to sort this https://github.com/dotnet/AspNetCore.Docs/issues/17517
                //services.Configure<IdentityOptions>(options =>
                //    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

                services.AddIdentityServer()
                    .AddApiAuthorization<ApplicationUser, RGDbContext>(options => {
                        options.IdentityResources["openid"].UserClaims.Add("name");
                        options.ApiResources.Single().UserClaims.Add("name");
                        options.IdentityResources["openid"].UserClaims.Add("role");
                        options.ApiResources.Single().UserClaims.Add("role");
                    });

                services.AddAuthentication()
                    .AddIdentityServerJwt();



                services.AddControllersWithViews();
                //services.AddRazorPages();
                services.AddRazorPages(options =>
                {
                    options.Conventions.AuthorizeAreaFolder("Management", "/Admin");

                });
                

                .... more of my own stuff...

*** The navigation to the server page works from a button in NavMenu firing an 'onclick' event to this:

    private void ServerPageTest()
    {
        Navigation.NavigateTo("/Management/Admin/Test", true);
    }

I have a feeling I am missing some options in my startup, any thoughts..

1

1 Answers

0
votes

I have the answer, I think...

Changing the Startup.cs file, where we have:

        services.AddAuthentication();

I changed it to:

        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
            options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
        })

This then allows the server to serve up razor pages, with authentication.

It also had another side effect of changing how the claims are perceived back at the server, and causes any API controllers to be able to work in a more traditional controller manner. I'll explain. I also have an 'ApplicationUserController' with an api endpoint 'AddUpdateUser', which does what it says on the tin.

I had this code to check the logged in user:

       public async Task<ActionResult<ApplicationUserDTO>> AddUpdateUser(ApplicationUserDTO sentUser)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        // Get the logged in user.
        // This line should work but doesnt and I dont know why.
        ApplicationUser loggedinUserX = await _userManager.GetUserAsync(User).ConfigureAwait(false);

But it was always returning null. and so I had to resort to finding the userID from the claims with this code:

        string loggedinUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        ApplicationUser loggedinUser = _context.Users.Find(loggedinUserId);

I'd had to search how to do this also. Found it on this great site of course.

But adding the two lines to the start up, the broke this code and made the original code work. I think I understand why now, and its all very well for folks to say 'read the documentation' but sometimes it's just too overwhelming.

Anyway, I hope this helps some people.