-1
\$\begingroup\$

I wrote a CustomCookieAuthenticationHandler because I don't like the way CookieAuthenticationHandler.HandleForbiddenAsync() manages results, in particular the fact that it redirects the user to another URL as I think the response should be returned to the URL that was requested.

I basically replaced the code with a status code setter to return 404 which is then handled by the error handler. I would like to know if this implementation is safe.

CustomCookieAuthenticationHandler

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;
using System.Net;
using System.Text.Encodings.Web;

namespace Frontend.Authorization.Handlers
{
    public class CustomCookieAuthenticationHandler(IOptionsMonitor<CookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder) : CookieAuthenticationHandler(options, logger, encoder)
    {
        /// Returns 404 instead of redirecting to the AccessDeniedPath
        protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
        {
            await Task.FromResult(Context.Response.StatusCode = (int)HttpStatusCode.NotFound);
        }
    }
}

CustomCookieExtensions (Copied from CookieExtensions, I just changed the authentication handler type)

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Frontend.Authorization.Handlers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extension methods to configure custom cookie authentication. Custom cookie authentication uses the default cookie authentication with some tweaks to the authentication handler used
/// </summary>
public static class CustomCookieExtensions
{
    public static AuthenticationBuilder AddCustomCookie(this AuthenticationBuilder builder)
        => builder.AddCustomCookie(CookieAuthenticationDefaults.AuthenticationScheme);

    public static AuthenticationBuilder AddCustomCookie(this AuthenticationBuilder builder, string authenticationScheme)
        => builder.AddCustomCookie(authenticationScheme, configureOptions: null!);

    public static AuthenticationBuilder AddCustomCookie(this AuthenticationBuilder builder, Action<CookieAuthenticationOptions> configureOptions)
        => builder.AddCustomCookie(CookieAuthenticationDefaults.AuthenticationScheme, configureOptions);

    public static AuthenticationBuilder AddCustomCookie(this AuthenticationBuilder builder, string authenticationScheme, Action<CookieAuthenticationOptions> configureOptions)
        => builder.AddCustomCookie(authenticationScheme, displayName: null, configureOptions: configureOptions);

    public static AuthenticationBuilder AddCustomCookie(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<CookieAuthenticationOptions> configureOptions)
    {
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
        builder.Services.AddOptions<CookieAuthenticationOptions>(authenticationScheme).Validate(o => o.Cookie.Expiration == null, "Cookie.Expiration is ignored, use ExpireTimeSpan instead.");
        return builder.AddScheme<CookieAuthenticationOptions, CustomCookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
    }
}

HomeController

    public class HomeController : Controller
    {
        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View();
        }
    }

Program.cs

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
    .AddCustomCookie(options =>
    {
        options.Cookie = new()
        {
            Name = "auth",
            SameSite = SameSiteMode.Lax,
            Path = "/",
            HttpOnly = true,
            IsEssential = true,
            MaxAge = TimeSpan.FromDays(6 * 30)
        };

        options.ExpireTimeSpan = TimeSpan.FromDays(6 * 30);
    });

app.UseStatusCodePagesWithReExecute("/Home/Error");
\$\endgroup\$
4
  • 1
    \$\begingroup\$ If you don't need async then don't use it. protected override Task HandleForbiddenAsync(AuthenticationProperties properties) could be implemented like this: { Context.Response.StatusCode = (int)HttpStatusCode.NotFound; return Task.CompletedTask; } \$\endgroup\$ Commented Oct 24, 2024 at 10:37
  • 1
    \$\begingroup\$ Could you please elaborate what do you mean by implementation is safe? From what perspective safe? \$\endgroup\$ Commented Oct 24, 2024 at 10:38
  • \$\begingroup\$ perhaps if current url passed to RedirectUri , then it will redirected to the same page!. if that is not enough, then maybe overriding CookieAuthenticationEvents.RedirectToAccessDenied would give more control on it. \$\endgroup\$ Commented Oct 24, 2024 at 10:55
  • \$\begingroup\$ @PeterCsala I mean if the check can be avoided by an attacker in any way. Of course this depends on wether the error handler doesn't let the request slip. \$\endgroup\$ Commented Oct 24, 2024 at 11:04

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.