Skip to content
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

Cannot override authentication in Microsoft.AspNetCore.Mvc.Testing #45608

Open
1 task done
kaylumah opened this issue Dec 15, 2022 · 11 comments
Open
1 task done

Cannot override authentication in Microsoft.AspNetCore.Mvc.Testing #45608

kaylumah opened this issue Dec 15, 2022 · 11 comments
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-testing MVC testing package investigate
Milestone

Comments

@kaylumah
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Override authentication as described in https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-7.0#mock-authentication using Microsoft.AspNetCore.Mvc.Testing does not work.

The issue appears to be similar to #37680 where configuration cannot be overridden via the new webapi templates (without startup.cs).

It appears that

builder.Services
    .AddAuthorization(options => { })
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer();

will go off after a ConfigureTestServices method in WebApplicationFactory and overriding it.
Omitting the above add authentication does trigger the the fake principal and authentication.

I created a repo case over here
https://github.com/kaylumah/WebApiOverrideConfigIssue

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

net7

Anything else?

No response

@kaylumah
Copy link
Author

To verify that other service can be overridden I also added a IWeatherService which gets replaced from the same ConfigureTestServices and that does work, so not sure what the difference is here :)

@javiercn javiercn added the area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Dec 15, 2022
@captainsafia
Copy link
Member

Triage: We need to investigate if the authentication options changes that we made in .NET 7 might have had a downstream impact on the testing overrides that happen here.

@ghost
Copy link

ghost commented Dec 15, 2022

To learn more about what this message means, what to expect next, and how this issue will be handled you can read our Triage Process document.
We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. Because it's not immediately obvious what is causing this behavior, we would like to keep this around to collect more feedback, which can later help us determine how to handle this. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact work.

@kaylumah
Copy link
Author

Not sure if the issue is just .net7 and newer, it also appears to be present in net6.0

Perhaps I missed something in the guide, or perhaps just like the linked issue regarding config the order of loading the testserver/normal app changed

@kaylumah
Copy link
Author

I am a step further in troubleshooting thanks to a comment on https://mazeez.dev/posts/auth-in-integration-tests

public class WebApp : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            // works
            services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = TestAuthHandler.AuthenticationScheme;
                    options.DefaultScheme = TestAuthHandler.AuthenticationScheme;
                    options.DefaultChallengeScheme = TestAuthHandler.AuthenticationScheme;
                })
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(TestAuthHandler.AuthenticationScheme, options => {});

            // does not work
            services.AddAuthentication(defaultScheme: TestAuthHandler.AuthenticationScheme)
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(TestAuthHandler.AuthenticationScheme, options => { });
        });
    }
}

Which means I can test authentication now.
However I get successfully authenticated now even with the following snippet

var factory = new WebApp();
        var client = factory.CreateDefaultClient();
        //client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme: TestAuthHandler.AuthenticationScheme);

As I understand it the AuthenticationHeaderValue is what should have triggered TestAuthHandler

@HaoK HaoK removed their assignment Feb 23, 2023
@sondli
Copy link

sondli commented Mar 11, 2023

I am a step further in troubleshooting thanks to a comment on https://mazeez.dev/posts/auth-in-integration-tests

public class WebApp : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            // works
            services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = TestAuthHandler.AuthenticationScheme;
                    options.DefaultScheme = TestAuthHandler.AuthenticationScheme;
                    options.DefaultChallengeScheme = TestAuthHandler.AuthenticationScheme;
                })
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(TestAuthHandler.AuthenticationScheme, options => {});

            // does not work
            services.AddAuthentication(defaultScheme: TestAuthHandler.AuthenticationScheme)
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(TestAuthHandler.AuthenticationScheme, options => { });
        });
    }
}

Which means I can test authentication now. However I get successfully authenticated now even with the following snippet

var factory = new WebApp();
        var client = factory.CreateDefaultClient();
        //client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme: TestAuthHandler.AuthenticationScheme);

As I understand it the AuthenticationHeaderValue is what should have triggered TestAuthHandler

I have the exact same issue, hope there will be some updates on this.

@captainsafia captainsafia added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-testing MVC testing package area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Jun 20, 2023
@nibblesnbits
Copy link

Has anyone looked into this recently? It's a big roadblock for me.

@codehunter13
Copy link

same problem over here...

@xzxzxc
Copy link

xzxzxc commented Nov 29, 2023

I think I have a workaround for the issue.

Description: As you can see here, AuthenticationMiddleware uses instances of IAuthenticationRequestHandler before using a default scheme. So instead of setting TestAuthHandler as default, we can make it implement IAuthenticationRequestHandler and, with a bit of copy-paste of internal code, do the same as AuthenticationMiddleware does.

Workaround:

  1. Replace the TestAuthHandler from the Mock authentication example with the following
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>, IAuthenticationRequestHandler
{
	public TestAuthHandler(
		IOptionsMonitor<AuthenticationSchemeOptions> options,
		ILoggerFactory logger,
		UrlEncoder encoder,
		ISystemClock clock)
		: base(options, logger, encoder, clock)
	{
	}

	public async Task<bool> HandleRequestAsync()
	{
		if (Request.Headers.Authorization != "TestScheme")
			return false;

		var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
		var identity = new ClaimsIdentity(claims, "Test");
		var principal = new ClaimsPrincipal(identity);

		var ticket = new AuthenticationTicket(principal, "TestScheme");

		var authFeatures = new AuthenticationFeatures(AuthenticateResult.Success(ticket));
		Context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
		Context.Features.Set<IAuthenticateResultFeature>(authFeatures);

		// return false, otherwise the AuthenticationMiddleware will finish the pipeline
		return false;
	}

	protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
	{
		// this should never be called
		throw new NotImplementedException();
	}

	/// <summary>
	/// Copied from https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationFeatures.cs
	/// </summary>
	internal sealed class AuthenticationFeatures : IAuthenticateResultFeature, IHttpAuthenticationFeature
	{
		private ClaimsPrincipal? _user;
		private AuthenticateResult? _result;

		public AuthenticationFeatures(AuthenticateResult result)
		{
			AuthenticateResult = result;
		}

		public AuthenticateResult? AuthenticateResult
		{
			get => _result;
			set
			{
				_result = value;
				_user = _result?.Principal;
			}
		}

		public ClaimsPrincipal? User
		{
			get => _user;
			set
			{
				_user = value;
				_result = null;
			}
		}
	}
}
  1. Remove the defaultScheme: "TestScheme" argument from the services.AddAuthentication call so it looks like
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication()
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "TestScheme", options => { });
            });

Now, you can control the authentication with the

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme: "TestScheme");

Tested in net6.0.

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@kaylumah
Copy link
Author

kaylumah commented Feb 8, 2024

@wtgodbe think something is broken with the both, it readds this label to this issue (which is not a PR..)

@wtgodbe
Copy link
Member

wtgodbe commented Feb 8, 2024

Sorry about that, please see #53859

@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@captainsafia captainsafia modified the milestones: .NET 8 Planning, Backlog Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-testing MVC testing package investigate
Projects
No open projects
Status: No status
Development

No branches or pull requests

9 participants