Add Auth Middleware

Implement auth middleware to ensure only authenticated users access sensitive APIs.

Now that your application is properly managing sessions, let's add middleware to verify that only users with valid sessions can access certain backend APIs.


Create Auth Middleware

To verify that frontend requests to your Session endpoint have an authenticated session, you can create an auth middleware that checks for a valid session cookie. Below is an example of an auth middleware function:

// AuthMiddleware.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Wristband.AspNet.Auth;

public class AuthMiddleware
{
    private readonly RequestDelegate _next;
    public AuthMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, IWristbandAuthService wristbandAuth)
    {
        // Only verify the session for endpoints with the RequireWristbandAuth attribute.
        // This allows for selectively choosing which endpoints require authentication.
        if (context.GetEndpoint()?.Metadata.GetMetadata<RequireWristbandAuth>() == null)
        {
            await _next(context);
            return;
        }

        // Verify that the request contained a valid session cookie.
        if (!await IsAuthenticated(context))
        {
            // If the request doesn't have a valid session cookie return a 401 response.
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        // Save the session in order to "touch" it and extend the session expiration window.
        await context.SignInAsync(
            CookieAuthenticationDefaults.AuthenticationScheme,
            context.User,
            new AuthenticationProperties { IsPersistent = true });
      
        await _next(context);
    }

    private async Task<bool> IsAuthenticated(HttpContext context)
    {
        var authResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        return authResult.Succeeded && authResult.Principal != null;
    }
}

Now configure your auth middleware in Program.cs. Make sure to place the auth middleware after UseAuthentication but before any protected routes are declared.

// Program.cs

...  
app.UseAuthentication();  
app.UseMiddleware<AuthMiddleware>(); // Enables your auth middleware.

// Protected routes below...



Enable Auth Middleware For Session Endpoint

To enable the auth middleware for the Session Endpoint, we need to add the RequireWristbandAuth attribute to its metadata. Also, since the session cookie is being validated by the middleware now, we can remove the logic from the Session Endpoint that was checking to see if the user was authenticated. The Session Endpoint implementation should now look like the following example:

// AuthRoutes.cs
using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Wristband.AspNet.Auth;

...

// Session Endpoint
app.MapGet("/session", (HttpContext httpContext) =>
{
    var user = httpContext.User;
  
    //
    // This check can be removed since the session is now validated in
    // the auth middleware
    //  
    // if (user?.Identity == null || !user.Identity.IsAuthenticated)
    // {
    //   return Results.Unauthorized();
    // }
    //
  
    return Results.Ok(new
    { 
      Email = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
    });
})
.WithMetadata(new RequireWristbandAuth());  // Add RequiresWristbandAuth metadata to protect the session endpoint with the auth middleware.

...

๐Ÿ“˜

Enable The Auth Middleware For All Protected Endpoints

Make sure to add the RequireWristbandAuth attribute to any other endpoints in your application that require an authenticated user session.

Note: The Login, Callback, and Logout Endpoints are meant to be accessed by unauthenticated users, so you don't need to add the RequireWristbandAuth attribute to them.


Whatโ€™s Next

Now that your server endpoints are protected, let's update the frontend code to handle unauthorized error responses.