Request Metrics won’t be very successful if users can’t sign up! We recently completed cookie based user authentication and distributed session using Redis but neglected initial sign up. Now we finally go back and do some boring forms and CRUD work.

A rich set of internal admin tools helps with customer support, system monitoring and visibility. We’ll start our suite of tools with a user admin page to list all users in the system. This page is access controlled using a simple filter attribute. While we’re here, we might as well let users sign out too.

The actual sign up logic is just a boring form submit and user data written to Redis. Because user CRUD was covered previously, we’ll skip it and jump to other interesting bits we ran into.

Simple Access Control with ActionFilterAttributes

Our new admin page should only be accessed by internal users. We could use the claims-based authorization built into ASP.NET Core for this, but it is a bit heavy for our taste. Instead, we use a simple ActionFilterAttribute attached to any controller or controller method that is access controlled:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Controllers
{
    [AdminOnly] // <--- Our custom attribute!
    public class AdminController : Controller
    {
        [HttpGet]
        [Route("/important-admin-stuff")]
        public async Task<IActionResult> GetImportantAdminScreen()
        {
            return Content("You have access to the admin page!")
        }
    }
}
AdminController.cs

The custom attribute just concerns itself with determining whether the currently authenticated user is an administrator:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace MyApp
{
    public class AdminOnlyAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            base.OnActionExecuting(context);

            if (!IsUserAnAdmin(context))
            {
                context.Result = new UnauthorizedResult();
            }
        }

        private bool IsUserAnAdmin(ActionExecutingContext context)
        {
            // Your application specific code here!
            // For example's sake, all authenticated users are considered admins.
            return context.HttpContext.User.Identity.IsAuthenticated;
        }
    }
}
AdminOnlyAttribute.cs

Sign Out a User and Clear Session

We have forgotten about sign out in the midst of all this signing in and signing up. A user should be de-authorized and their session cleared when they sign out. We settle for clearing all data contained within the user’s session because ASP.NET Core no longer has a way to abandon or destroy a session:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Controllers
{
    public class LoginController : Controller
    {
        [HttpPost]
        [Route("/signout")]
        public async Task<IActionResult> SignOut()
        {
            // Signout of cookie based auth
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            // Clear all data from the user's session.
            // NOTE: This does not destroy the session itself!
            HttpContext.Session.Clear();

            return this.Redirect("/login");
        }

        // ...snip (other login methods here)...
    }
}
LoginController.cs

User sign up and authentication is done for now. Next, we’ll change gears and start working on the Request Metrics JavaScript browser agent!