Skip to content

No machineKey node in Web.config #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
br3nt opened this issue Oct 14, 2022 · 6 comments
Closed

No machineKey node in Web.config #30

br3nt opened this issue Oct 14, 2022 · 6 comments

Comments

@br3nt
Copy link

br3nt commented Oct 14, 2022

How do I find the required cryptographic values if the machineKey node is not present in Web.config.

In fact, I can't seem to find any source of cryptographic values used by FormsAuthentication in my existing APS.NET application.

P.S. thank you for your suggestion to use your library over at dotnet/systemweb-adapters#228.

@dazinator
Copy link
Owner

@br3nt if it's not in your apps web.config, maybe it's in the machine level web.config:

Machine.config can be found in C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config (or a different v-folder if you're using a different .NET version) –

@dazinator
Copy link
Owner

Note: if your Web app is relying on auto generated machine keys then they will be a bit more difficult to obtain. I've not had to do it personally because In my experience the machine keys have been specified in web.config. However some searching around suggests the auto generated key should be possible to obtain: https://stackoverflow.com/questions/1755130/getting-the-current-asp-net-machine-key
Failing that you could introduce the machine key and set it to something you control.. may or not be possible in your situation given it would break existing logins.

@br3nt
Copy link
Author

br3nt commented Oct 17, 2022

The solutions from https://stackoverflow.com/a/35954339/848668 and https://stackoverflow.com/a/31919794/848668 managed to get me the required decryption key and validation key, with ShaVersion.Sha256, and CompatibilityMode.Framework45 passed to the LegacyFormsAuthenticationTicketEncryptor constructor.

However, if I manually added the machineKey node to Web.config, only the solution from https://stackoverflow.com/a/31919794/848668 provided me with the correct keys. I find that really odd.

@br3nt br3nt closed this as completed Oct 17, 2022
@dazinator
Copy link
Owner

@br3nt glad you figured it out

@br3nt
Copy link
Author

br3nt commented Oct 18, 2022

Just for completeness, here's the authentication setup:

public class LegacyAspNetCookieAuthenticationHandler : AuthenticationHandler<LegacyAspNetCookieAuthenticationHandlerOptions>
{
    public static string SchemeName = "Legacy ASP.NET Cookie";

    public static string AuthenticationType { get; set; } = SchemeName;
    public static string AuthenticationScheme { get; set; } = SchemeName;

    public LegacyAspNetCookieAuthenticationHandler(IOptionsMonitor<LegacyAspNetCookieAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var authCookie = Request.Cookies[".ASPXAUTH"];
        if (string.IsNullOrWhiteSpace(authCookie)) return Task.FromResult(AuthenticateResult.Fail("Not logged in. No authentication cookie"));
        var authTicket = DecryptCookie(authCookie!);
        var result = AuthenticateTicket(authTicket);

        return Task.FromResult(result);
    }

    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        return base.HandleChallengeAsync(properties);
    }

    protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
    {
        return base.HandleForbiddenAsync(properties);
    }

    public AuthenticateResult AuthenticateTicket(FormsAuthenticationTicket? authTicket)
    {
        if (authTicket is null) return AuthenticateResult.Fail("Not logged in. Couldn't decrypt authentication cookie");
        if (authTicket!.Expired) return AuthenticateResult.Fail("Not logged in. Authentication session expired");

        var claims = new List<Claim>()
        {
            new Claim(ClaimTypes.NameIdentifier, authTicket.Name),
        };

        var identity = new ClaimsIdentity(claims, AuthenticationType);
        var identities = new List<ClaimsIdentity> { identity };
        var principal = new ClaimsPrincipal(identities);
        var ticket = new AuthenticationTicket(principal, AuthenticationScheme);

        return AuthenticateResult.Success(ticket);
    }

    public FormsAuthenticationTicket? DecryptCookie(string cookie)
    {
        try
        {
            var decryptionKey = OptionsMonitor.CurrentValue.DecryptionKeyBytes;
            var validationKey = OptionsMonitor.CurrentValue.ValidationKeyBytes;
            var shaVersion = OptionsMonitor.CurrentValue.ShaVersion;

            var encryptor = new LegacyFormsAuthenticationTicketEncryptor(decryptionKey, validationKey, shaVersion, CompatibilityMode.Framework45);
            return encryptor.DecryptCookie(cookie);
        }
        catch (Exception e)
        {
            // TODO Add logging
        }

        return null;
    }
}```

Program.cs:

```c#
var authenticationOptions = builder.Configuration.GetSection("LegacyAspNetCookieAuthentication");
builder.Services
    .Configure<LegacyAspNetCookieAuthenticationHandlerOptions>(authenticationOptions)
    .AddScoped<IAuthenticationHandler, LegacyAspNetCookieAuthenticationHandler>()
    .AddAuthentication(options =>
    {
        options.DefaultScheme = LegacyAspNetCookieAuthenticationHandler.SchemeName;
        options.DefaultAuthenticateScheme = LegacyAspNetCookieAuthenticationHandler.SchemeName;
        options.DefaultChallengeScheme = LegacyAspNetCookieAuthenticationHandler.SchemeName;
    })
    .AddScheme<LegacyAspNetCookieAuthenticationHandlerOptions, LegacyAspNetCookieAuthenticationHandler>(LegacyAspNetCookieAuthenticationHandler.SchemeName, null)
    ;

// after app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

@br3nt
Copy link
Author

br3nt commented Oct 18, 2022

If HandleAuthenticateAsync returns with an AuthenticateResult.Fail() response, HandleChallengeAsync() will get hit next.

We can redirect the user to a login page using the following method taken from the ASP.NET Core cookie authentication implementation.

// from https://github.com/dotnet/aspnetcore/blob/1b15d0e92f139d853b5a1b740cd571d9d08e672f/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs#L92
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
    var loginUri = "/"; // this should come from `Options`

    if (IsAjaxRequest(Request))
    {
        Response.Headers.Location = loginUri;
        Response.StatusCode = 401;
    }
    else
    {
        Response.Redirect(loginUri);
    }
    return Task.CompletedTask;
}

// from https://github.com/dotnet/aspnetcore/blob/1b15d0e92f139d853b5a1b740cd571d9d08e672f/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs#L105
public static bool IsAjaxRequest(HttpRequest request)
{
    return string.Equals(request.Query[HeaderNames.XRequestedWith], "XMLHttpRequest", StringComparison.Ordinal) ||
        string.Equals(request.Headers.XRequestedWith, "XMLHttpRequest", StringComparison.Ordinal);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants