Authentication null with JUnit 5 testing Micronaut 3.3.4

560 Views Asked by At

Trying to protect the controller with micronaut security using JUnit testing, getting authentication as NULL.

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@MicronautTest
public class ProductCreateTest extends TestContainerFixture {
    private BearerAccessRefreshToken bearerAccessRefreshToken = null;
    @Inject
    @Client("/")
    HttpClient client;
    private ProductModel productModel;

    @Test
    @Order(1)
    @DisplayName("Should create a JWT token")
    void shouldCreateAJwtToken() {
        UsernamePasswordCredentials creds = new UsernamePasswordCredentials("[email protected]", "RockStar.1");
        HttpRequest request = HttpRequest.POST("/login", creds);
        HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken.class);
        bearerAccessRefreshToken = rsp.body();
    }

    @Test
    @Order(2)
    @DisplayName("Should create the product")
    void shouldCreateTheProduct() {
        this.productModel = new ProductModel(
                null,
                Optional.of(true),
                Optional.of(false),
                "Nike shirt",
                "Nike shirt description",
                Optional.of(new CategoryModel(new ObjectId().toString(), "Men", "Men")),
                Optional.of(new SubCategoryModel(new ObjectId().toString(), "Men", "Men")),
                Optional.of(new VendorModel(new ObjectId().toString(), "Men", "Men")),
                Optional.of(List.of(new SelectOptionModel("Shirt", "Shirt"))),
                Optional.of(false)
        );

        HttpRequest request = HttpRequest.POST("/product", this.productModel)
                .header("Authorization",bearerAccessRefreshToken.getAccessToken());
        HttpResponse<ProductModel> rsp = client.toBlocking().exchange(request, ProductModel.class);
        var item = rsp.body();
    }
}

Authentication Provider

@Singleton
@Requires(env = Environment.TEST)
public record AuthenticationProviderUserPasswordFixture() implements AuthenticationProvider {
    @Override
    public Publisher<AuthenticationResponse> authenticate(HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
        return Flux.create(emitter -> {
            if (authenticationRequest.getIdentity().equals("[email protected]") && authenticationRequest.getSecret().equals("RockStar.1")) {
                emitter.next(AuthenticationResponse.success((String) authenticationRequest.getIdentity(), List.of("roles1", "roles2")));
                emitter.complete();
            } else {
                emitter.error(AuthenticationResponse.exception());
            }
        }, FluxSink.OverflowStrategy.ERROR);
    }
}

Controller defination

@Post
@IRequirement(resourceName = ClaimType.TAG_PRODUCT, permission = {ClaimValue.TAG_OWNER,ClaimValue.TAG_CREATOR,ClaimValue.TAG_MAINTAINER })
Mono<MutableHttpResponse<?>> post(@Body @Valid ProductModel model);

Security Rule

@Singleton
public class AuthorityHandler implements SecurityRule {
    @Override
    public Publisher<SecurityRuleResult> check(HttpRequest<?> request, @Nullable RouteMatch<?> routeMatch, @Nullable Authentication authentication) {
        if (routeMatch instanceof MethodBasedRouteMatch methodBasedRouteMatch) {
            if (methodBasedRouteMatch.hasAnnotation(IRequirement.class)) {
                AnnotationValue<IRequirement> requiredPermissionAnnotation = methodBasedRouteMatch.getAnnotation(IRequirement.class);
                Optional<String> resourceIdName = requiredPermissionAnnotation.stringValue( "resourceName");
                String[] permissions = requiredPermissionAnnotation.stringValues("permission");
                if (permissions.length > 0 && resourceIdName.isPresent() && authentication != null) {
                    Map<String, Object> identityClaims = authentication.getAttributes();
                    if (Arrays.stream(permissions).anyMatch(element ->  identityClaims.containsValue(element)))
                        return Mono.just(SecurityRuleResult.ALLOWED);
                    else
                        return Mono.just(SecurityRuleResult.REJECTED);
                }
            }
        }
        return Mono.just(SecurityRuleResult.UNKNOWN);
    }
}

In the security rule the check method The Authentication authentication is null. However, from the Authentication Provider, I have successful authentication.

1

There are 1 best solutions below

1
On

Adding security logs to the logback.xml gives me a message that I was missing bearer in front of access token

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <withJansi>true</withJansi>
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
        <pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n
        </pattern>
    </encoder>
</appender>

<root level="info">
    <appender-ref ref="STDOUT"/>
</root>
<logger name="io.micronaut.http.client" level="TRACE"/>
<logger name="io.micronaut.security" level="TRACE"/>

Adding bearer to the header solve the issue

 HttpRequest request = HttpRequest.POST("/product", this.productModel)
                .header("Authorization","bearer "+bearerAccessRefreshToken.getAccessToken());
        HttpResponse<ProductModel> rsp = client.toBlocking().exchange(request, ProductModel.class);
        var item = rsp.body();