Design, Test and Code like it’s heaven on Earth - Part 20

Published on 11/13/2019

In this part I’m gearing up to prepare this site for launch, so that you can read this blog on the very site that is being built. There are two major shortcomings that I need to address.

  • Authentication for the administrative area
  • Migrating all my previous blogs to the new database

A third, but less important issue is to resolve hosting of local images and displaying them correctly. I’ll dive into the security first.

Concept: Spike

This is going to be a slightly different flow, because I’ll be leveraging on many existing packages and libraries, and not so much writing my own code. This means unit testing is taking a back seat until I’ve figured this out. Typically we would refer to this as a spike, and it is very different from a feature. A spike is part investigation, part R&D, part trial and error, and part prototype. It is important to stay disciplined here though, and not integrate your spike into production code. You have to clean it up completely if you intend to use it as is. When you are done, no code-reviewer must be able to ascertain that here you wrote code first and tests later. This is why it is typically better to throw away the spike and implement the feature properly from scratch, using only the learnings from the spike.

Single Sign-On

I have a Google account, and I want to authenticate using that instead of saving passwords and managing an identity manually. This requires that I setup an app on Google Cloud, because I have to authorize my own app to get access through my Google account.

If you are unfamiliar with the protocol and flow of OAuth and OpenID-Connect, don’t be alarmed. These are not trivial concepts at all. I won’t go into explaining the protocol in detail (you can read all about it here), but I will take you through what I have to do to leverage off of Google’s authentication services.

First off, review Google’s documentation on using their OAuth services. There are quite a few steps to complete here, but ultimately what we need to do on our end involves managing the super secret token that identifies your app correctly, and getting the redirect and user management right. Firstly, let’s setup the user secret for the token. I start by defining my required config in my appsettings.json file.

  "Authentication": {
    "Google": {
      "ClientId": "<some value>",
      "ClientSecret": "<some value>"
    }
  }

User Secrets

In .NET Core they introduced User Secrets to make it really easy to not include passwords and tokens and other sensitive information in your config which is committed to source control. In Visual Studio, start by right clicking on the project that contains the configuration, and select “Manage User Secrets”. This will generate a GUID for your project and open a config file that manages the contents for this specific project’s secrets in a file in your user account. See the official documentation for how this is managed. Secret management is a thing. If you’re not using VS or .NET, do the effort to go and find and understand your specific development environment’s conventions and systems that does this.

Mange User Secrets

You can include any settings you want in this config file. I’m going to include only my app token and secret for now.

{
  "Authentication:Google:ClientId": "<your actual value>",
  "Authentication:Google:ClientSecret": “<your actual value>”
}

So how does this replace the value in your actual config? Well, technically it doesn’t. There’s no transform that happens here that changes the config file. But .NET Core’s default app builder includes a hierarchy of config loaders that all in turn overrides previously loaded config. The documentation for this is pretty clear. This means that each developer can easily manage their own local connection strings and other settings for their machine. Configuration is a thing. If you’re not using .NET Core, do the effort to go and find and understand your specific development environment’s conventions and systems that does this.

But prod?

Obviously these user secret files doesn’t exist on any actual deployment environment, including production. Then you’ll rely on one of the other mechanisms. In a docker environment you would typically load it from pre-configured environment variables, like from a .env file, or refer to a vault service like Azure vault, or set the AppServices app settings. Or, perhaps just starting Kestrel from the command line and providing them right there using parameters.

To do a very simple test that you’re getting the required value through correctly, simply trap it with a variable and a breakpoint.

Test User Secrets

Authentication

Now we need to add authentication, and .NET Core has some built-in service provider implementations for this. Together with the authentication documentation and this piece by Gunnar Peipman, it becomes clear that the Blazor-with-auth template is also very much tied directly into ASP.NET Identity, just like the MVC template. I’m not interested in maintaining a user record at all. I don’t want password reset screens or even a login screen. This means I can’t use the built-in services and I’ll have to manage my own authentication and authorization. But there are a view items that I can lift out of those posts and the template, like the AuthorizeView tag for example. Let’s just stick to cookie authorization for now.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication()
        .AddCookie();
    services.AddAuthorization();    
    ...// the rest of the configuration
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...//the rest of the pipeline setup
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
}

I make a change to the blog publish page to include the AuthorizeView tag. This is what tells the Blazor engine (or view renderer) to check the state of the user on the context. A direct analogy for this would be the [Authorize] attribute that we decorate our controller actions with. The <ChildContent> tag was something I added after VS presented an error to me in the IDE. Both AuthorizeVew and EditForm have a property for Context and the compiler can’t resolve it. So I have to map the inner one to a different name - I simply followed the suggested change as explained in the very helpful error message.

The child content element 'ChildContent' of component 'EditForm' uses the same parameter name ('context') as enclosing child content element 'Authorized' of component 'AuthorizeView'. Specify the parameter name like: ' to resolve the ambiguity

Now that is a very helpful error message, and it allows me to solve it myself without having to google or ask a question on Stack Overflow. That’s great, and it’s a great example of how your error messages should be structured and the experience they should give.

Here is my page after all that.

<AuthorizeView>
    <Authorized>
        @if (Model == null)
        {
            <p><em>Loading...</em></p>
        }
        else
        {
            <EditForm Model="@Model" OnValidSubmit="Submit">
                <ChildContent Context="editContext">
                    <DataAnnotationsValidator />
                    <ValidationSummary />
                    <p>
                        <label for="title">Title:</label>
                        <InputText Id="title" bind-Value="@Model.Title" />
                    </p>
                    <p>
                        <label for="isPublished">Is Published:</label>
                        <InputCheckbox Id="isPublished" bind-Value="@Model.IsPublished" />
                    </p>
                    <p>
                        <label for="publishDate">Publish Date:</label>
                        <InputDate T="System.DateTime?" Id="publishDate" bind-Value="@Model.PublishDate" />
                    </p>
                    <p>
                        <label for="content">Content:</label>
                    </p>
                    <p>
                        <InputTextArea Id="content" bind-Value="@Model.Content" Class="textarea-content" />
                    </p>
                    <p>
                        <button type="submit">Submit</button>
                    </p>
                </ChildContent>
            </EditForm>
        }
    </Authorized>
    <NotAuthorized>
        <p><em>New site, who dis?</em></p>
    </NotAuthorized>
</AuthorizeView>

After some troubleshooting, I come across this post which talks about the CascadingAuthenticationState and how that should be used. Of course I created my project on preview 4 still, so I clearly didn’t lift all the items that I needed to from the template before. This is how you learn about the mechanics of a new technology. I now know and understand that Blazor (on server-side at least) follows a cascading model of state down to the various components. I like how Blazor is a good proxy for React, and how a lot of the concepts cary over, especially the emphasis on components, and now also a cascading state (or context in this case).

When I browse to my publish page, I get the following, and it’s perfect.

Not Authorized

The Front-End part (sort of)

Now we have to do the sign-on negotiation. Google provides their authentication service according to the OpenID Connect standard of course, and their documentation is mostly straight forward on how you deal with this. I need to ascertain if their provided libraries and the Web Application example are of interest and practical in the Blazor context. I will probably have to add a specific controller. But there is another source of information that I’m looking at: Dan Roth’s BlazingPizza demonstration app. He built this as part of the .NET Core presentations at Build, and in it he includes authentication against a Microsoft account. His solution is an amalgamation of Nuget packages, Javascript and Blazor components. He also has a specific controller through which auth is initiated, so my suspicion was accurate. I’m certain this implementation is pretty much hacked together, and that a more elegant pattern will emerge as they finalize the Blazor framework.

So after a few days I got it mostly working. I copied the auth controller from the pizza project. I also had to include the controllers mapping for the new endpoint middleware.

public class AuthController : Controller
{
    [HttpGet("auth/user")]
    public UserState GetUser()
    {
        return new UserState
        {
            IsLoggedIn = User.Identity.IsAuthenticated,
            DisplayName = User.Identity.Name,
            PictureUrl = User.FindFirst("picture")?.Value
        };
    }

    [HttpGet("auth/signin")]
    public async Task SignIn()
    {
        await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme);
    }

    [Authorize]
    [HttpGet("auth/signincompleted")]
    public IActionResult SignInCompleted()
    {
        var userState = GetUser();
        return Content($@"
            <script>
                window.opener.onLoginPopupFinished({JsonConvert.SerializeObject(userState)});
                window.close();
            </script>", "text/html");
    }

    [HttpPut("auth/signout")]
    public async Task<UserState> SignOut()
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        return UserState.LoggedOutState;
    }
}
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

This controller is invoked by some javascript. That javascript is in a separate file in the wwwroot folder. I basically copied that too, and included it into the _Host.cshtml body section.

<body>
    <app>@(await Html.RenderComponentAsync<App>())</app>

    <script src="~/js/authPopup.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>

I didn’t change anything in the javascript except where it goes for the authentication call. Once the javascript calls into the controller, I basically changed the SignIn() method call to challenge using the GoogleDefaults.AuthenticationScheme value instead of the Microsoft one in the pizza demo. So to make this work, I instead added the provided Google authentication to my services. I copied this from the pizza demo too and swapped Microsoft for Google.

services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; })
    .AddCookie()
    .AddGoogle(googleOptions =>
    {
        googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
        googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
        googleOptions.CallbackPath = "/auth/signincompleted";
        googleOptions.Events.OnCreatingTicket = (context) =>
        {
            string pictureUrl = context.User.GetProperty("picture").GetString();
            context.Identity.AddClaim(new Claim("picture", pictureUrl));
            return Task.CompletedTask;
        };
    });

There are additional code here that extracts the picture URL from the Google profile. It is already included in the scopes, but somehow doesn’t make its way into the set of claims on the HttpContext. This code adds it manually. I also change the callback route to the one that is provided by my controller. This callback route is also configured in my Google Cloud account for this app. And finally, to get it all tied up, I made a quick addition to the top bar of my app to include the section that was in the pizza’s ForceSigninLayout.razor component. I copied a lot of this from the pizza demo of course.

@inherits LayoutComponentBase
@inject HttpClient HttpClient
@inject IJSRuntime JSRuntime
@using helloserve.com.Auth
@using Microsoft.JSInterop

@if (userState == null) // Retrieving the login state
{
    <text>Loading...</text>
}
else
{
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <div class="top-row px-4">
            <span>@userState.IsLoggedIn</span>
            <AuthorizeView>
                <Authorized>
                    <span><img src="@userState.PictureUrl" width="32" height="32" /></span>
                    <span>&nbsp;</span>
                    <span>@userState.DisplayName</span>
                    <span>&nbsp;</span>
                    <span>
                        <button class="btn btn-info" onclick="@SignOut">Sign out</button>
                    </span>
                </Authorized>
                <NotAuthorized>
                    <span>
                        <button class="btn btn-danger" onclick="@SignIn">Sign in</button>
                    </span>
                </NotAuthorized>
            </AuthorizeView>
            <a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank" class="ml-md-auto">About</a>
        </div>

        <div class="content px-4">
            @Body
        </div>
    </div>
}

@functions{
    [CascadingParameter] Task<AuthenticationState> authStateTask { get; set; }

    private List<TaskCompletionSource<bool>> pendingSignInFlows = new List<TaskCompletionSource<bool>>();
    UserState userState = new UserState();

    protected override async Task OnInitAsync()
    {
        var authState = await authStateTask;
        if (authState.User != null)
        {
            userState.DisplayName = authState.User.Identity.Name;
            userState.PictureUrl = authState.User.FindFirst("picture")?.Value;
            userState.IsLoggedIn = authState.User.Identity.IsAuthenticated;
        }

    }

    public async Task SignIn()
    {
        await JSRuntime.InvokeAsync<object>("openLoginPopup", DotNetObjectRef.Create(this));
    }

    public async Task SignOut()
    {
        // Transition to "loading" state synchronously, then asynchronously update
        userState = null;
        StateHasChanged();

        userState = await HttpClient.PutJsonAsync<UserState>("auth/signout", null);
        StateHasChanged();
    }

    [JSInvokable]
    public void OnSignInStateChanged(UserState newUserState)
    {
        userState = newUserState;
        StateHasChanged();

        foreach (var tcs in pendingSignInFlows)
        {
            tcs.SetResult(newUserState.IsLoggedIn);
        }
        pendingSignInFlows.Clear();
    }
}

They have it in a separate component. So I took what they did there and applied it to my MainLayout.razor component, and included all the required functions they bundled into the UserStateProvider.razor component. This is where the pizza app and Blazor in .NET Core 3.0 preview 6 start to diverge. The framework now provides the <AuthorizeView> tag, and I don’t have to do manual if else checks as part of the components. But this also means that I need to add the CascadingAuthenticationState to my app. In the pizza app they built a manual provider (the UserStateProvider component) and added that as a cascading state.

Ultimately I used almost everything from pizza demonstration, and none of the official Google Nuget packages was helpful (or even functional). For example, there’s some part of their Nuget package that doesn’t implement a required interface, so it couldn’t even start up.

So how does this work?

Here’s the flow as far as I can work out:

  • The main page loads, and the <AuthorizeView> tag renders the <NotAuthorized> section, showing the sign-in button.
  • The user clicks this button which is wired up to the SignIn method in the component, which in turn does a JSRuntime.InvokeAsync.
  • This executes the javascript function to show the popup window, which in turn POSTs into AuthController.SignIn().
  • This controller action then issues a challenge to the specified authentication service. The challenge results in a redirect that somehow ends up in the popup window. I’m not sure how that works since the controller’s return type is void. Perhaps internally the response status is manually set to redirect. It redirects to Google’s OpenID Connect endpoint, and the user now sees the login popup (or the consent screen).
  • The user selects their account or logs into their account. Google calls us back on the configured callback URI. This is set to be AuthController.SignInCompleted(). This returns with a small snippet of Javascript which closes the popup and invokes a method on the component again, the OnSignInStateChanged method.
  • This method accepts the new user state and signals the component that the state has changed.

You can see how the flow starts with the MainLayout component, goes through our controller, through Google, and then follows the path back through our controller back to the component.

What doesn’t work?

So all in all this mostly works. Somehow I managed to get this flow into a state where it bounces between the SignIn and SignInComplete and the popup never actually shows, but the user is authenticated. The sign out doesn’t work effectively. The state is changed, but the <AuthorizeView> section always renders the authorized section after initial sign-on, regardless of signing out.

Concept: Authentication vs Authorization

However, this will allow any person with a Google account to authenticate on my personal site. So this means that the default authorization needs to be changed. These are two separate concepts, and exist to deal with two very different aspects of your program. Authentication is all about who you are, while authorization is all about what you can do. I’ll gladly accept that you are who Google says you are, but I’ll certainly not want you authoring blogs on my site. I added the following authorization handler to deal with this:

public class helloserveAuthorizationHandler : AuthorizationHandler<helloserveAuthorizationRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, helloserveAuthorizationRequirement requirement)
    {
        string email = context.User.FindFirst(ClaimTypes.Email)?.Value;
        if (!string.IsNullOrEmpty(email))
        {
            if (email != "<some email address>")
            {
                context.Fail();
            }
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class helloserveAuthorizationRequirement : IAuthorizationRequirement { }

public class helloserveAuthorizationHandlerDefaults
{
    public const string AuthorizationPolicy = "helloserve";
}

In my Startup.cs file, I include this handler as per the standard implementation pattern for policy based authorization (there are other types too - see the sidebar on the docs site for resource based, view based etc).

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicy(
        new List<IAuthorizationRequirement>() 
        {
            new helloserveAuthorizationRequirement() 
        }, 
        new List<string>() 
        { 
            helloserveAuthorizationHandlerDefaults.AuthorizationPolicy 
        });
});
services.AddScoped<IAuthorizationHandler, helloserveAuthorizationHandler>();

Having this in place prevents the render of the <Authorized> section inside the <AuthorizeView> tags if you don’t match "some email address". Now I can control who can access the administrative section of my site. This code is currently hard coding a specific email address for testing purposes, but ultimately I’ll move this into configuration. I don’t want to have a user table (or maintain a set of users in a database) at all. It should be pretty obvious that in this handler I can inject whatever I need in order to facilitate the email address lookup and check.

Conclusion

This was a true R&D piece of work, and you probably didn’t learn a lot from it. Perhaps the code snippets will be of some use to someone, although this piece was authored on .NET Core 3.0 preview 6, while .NET Core has been out for some time. There remains a bit of cleaning up around this code, and some thought must be put into how the sign-in will be initiated, obviously I don’t want to have a sign-in button displayed on any of the public pages or content areas. We briefly discussed the differences in Authentication and Authorization. The former is a pretty straight forward concept, and can include things like second factors as well. The latter can become a bit of a complicated topic, and there are many different implementations and methods. We only looked at policy-based authorization.

As you get more experience in software development, you’ll find that authentication and authorization (especially authorization) becomes very important pillars of your solution or system. If authentication or authorization fails it erodes the trust of your users. It also has a demotivational effect when someone is supposed to be able to do something, but cannot. This is a less talked about aspect of software development, of how it directly affects people as opposed to productivity. And in larger publicly facing systems, it can turn public sentiment against you on a dime.