I have a WebAPI on ASP.NET and IdentityServer4 project for authorization, registration and all of this stuff. So, right now i creating integration tests for this API and i cant reach API methods that have an [Authorize] tag and i get Unauthoruzed:401 exception on test. For example WebAPI method:
[ProducesResponseType(typeof(ChangePasswordResponse), 200)]
[ProducesResponseType(typeof(ChangePasswordResponse), 400)]
[Authorize]
[HttpPost("change-password")]
public async Task<ActionResult<ChangePasswordResponse>> ChangePassword([FromBody] ChangePasswordRequest request)
{
_logger.LogInformation("***");
var model = _mapper.Map<ChangePasswordModel>(request);
var response = await _userAccountService.ChangePassword(model);
if (response == null)
return BadRequest(response);
return Ok(response);
}
And test is:
[Fact]
public async Task ChangePassword_Returns200Response()
{
// Arrange;
var model = new ChangePasswordRequest
{
Email = StudentConsts.Email,
CurrentPassword = StudentConsts.Password,
NewPassword = StudentConsts.NewPassword
};
var request = _sutDataHelper.GenerateRequestFromModel(model);
// Act
var response = await _client.PostAsync("accounts/change-password", request);
var responseContent = response.Content.ReadAsStringAsync().Result;
var content = JsonSerializer.Deserialize<ChangePasswordResponse>(responseContent);
// Asserts
response.EnsureSuccessStatusCode();
content.Email.Should().Be(model.Email);
content.UserName.Should().Be(StudentConsts.UserName);
}
Integration tests is created with IClassFixture<CustomWebApplicationFactory>
and i dont put any code for authentication from WebAPI but when i put this code from WebAPI i getting errors like: SchemeAlreadyExists: Identity.Application
or SchemeAlreadyExists: Bearer
Services setup code for authorization:
services
.AddIdentity<User, IdentityRole<Guid>>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddUserManager<UserManager<User>>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddJwtBearer(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = "HERE IS IDENTITYSERVER4 SERVICE URL";
options.Authority = "HERE IS IDENTITYSERVER4 SERVICE URL";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
options.Audience = "api";
});
services.AddAuthorization(options =>
{
options.AddPolicy(AppScopes.OpenId, policy => policy.RequireClaim("scope", AppScopes.OpenId));
options.AddPolicy(AppScopes.Profile, policy => policy.RequireClaim("scope", AppScopes.Profile));
options.AddPolicy(AppScopes.Email, policy => policy.RequireClaim("scope", AppScopes.Email));
...
});
When i add another AnotherWebApplicationFactory file IDE just telling me that Program class is exists in WebAPI and IS4 project and maybe that means that i cant create 2 project at the same time for integration tests for getting access_token from http://host:00/connect/token identityserver4 link. Also i cant just hard code fake access_token from other user i register in WebAPI by yourself cuz he is telling me that token not allowed or something like this. I just started learning integration tests and getting stack on authorization.
SOLVED BY THIS SOLUTION: https://question-it.com/questions/418799/kak-otkljuchit-vkljuchit-autentifikatsiju-vo-vremja-vypolnenija-v-aspnet-core-22
Clearly added policies to services setup in integration tests with AuthRequirement and AuthRequirementHandler.
Services Setup:
services.AddScoped<IAuthorizationHandler, MaintenanceModeDisabledOrAuthenticatedUserRequirementHandler>();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddRequirements(new MaintenanceModeDisabledOrAuthenticatedUserRequirement())
.Build();
options.AddPolicy(AppScopes.OpenId, builder => builder
.AddRequirements(new MaintenanceModeDisabledOrAuthenticatedUserRequirement()));
options.AddPolicy(AppScopes.Profile, builder => builder
.AddRequirements(new MaintenanceModeDisabledOrAuthenticatedUserRequirement()));
options.AddPolicy(AppScopes.Email, builder => builder
.AddRequirements(new MaintenanceModeDisabledOrAuthenticatedUserRequirement()));
});
AuthRequirement:
public class MaintenanceModeDisabledOrAuthenticatedUserRequirement : IAuthorizationRequirement
{ }
AuthRequirementHandler:
public class MaintenanceModeDisabledOrAuthenticatedUserRequirementHandler : AuthorizationHandler<MaintenanceModeDisabledOrAuthenticatedUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MaintenanceModeDisabledOrAuthenticatedUserRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
Hope you solved your problem!