Search code examples
c#asp.net-web-apiintegration-testingidentityserver4xunit

How to get access_token for integration tests for passing [Authorize] tag? ASP.NET WebAPI


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.


Solution

  • 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!