0

Using micronaut to create rest end-point which is protected by using micronaut-jwt security

        @Post
        @IRequirement(resourceName = ClaimType.TAG_PRODUCT, permission = {ClaimValue.TAG_OWNER,ClaimValue.TAG_CREATOR,ClaimValue.TAG_MAINTAINER })
        Mono<MutableHttpResponse<?>> post(@Body @Valid ProductModel model){
        LOG.info(String.format("Creating new product"));
        return _iServiceBus.<ProductModel, Mono<ProductModel>>send(model).flatMap(item -> {
                    if (item != null) {
                        try {
                            return Mono.just(HttpResponse.created(item, new URI(String.format("/%s", item.id()))));
                        } catch (URISyntaxException e) {
                            return Mono.error(new GlobalException(e));
                        }
                    } else
                        return Mono.just(HttpResponse.serverError());
                }
        ).onErrorMap(throwable -> {
            throw new GlobalException(throwable);
        });}

Security model

@Inherited
public @interface IRequirement {
    String resourceName();
    String[] permission();
}

Security rules

@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);
    }
}

The above end-point is protected as claims owner,creator and maintainer

I am using Identity server 4 (https://github.com/IdentityServer/IdentityServer4) to manage the identity and when the user logins in it contain the following claims in the access token

{
  "nbf": 1641548846,
  "exp": 1641549046,
  "iss": "https://localhost:5001",
  "client_id": "Falcon_Identity_Server",
  "sub": "673533cc-7c0b-40f3-80ac-222696df385d",
  "auth_time": 1641548840,
  "idp": "local",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "673533cc-7c0b-40f3-80ac-222696df385d",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "admin@local.com",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "admin@local.com",
  "AspNet.Identity.SecurityStamp": "812aa4cb-b9f1-48ac-9e39-1f0dceb6f1c4",
  "identityserver": "owner",
  "fb_product": "owner",
  "fb_order": "owner",
  "fb_payment": "owner",
  "jti": "179683F5CCA01EB925B45B1CA6379080",
  "sid": "F1D9F917D34178EDAF7A5FD24948F89D",
  "iat": 1641548846,
  "scope": [
    "openid",
    "profile",
    "email"
  ],
  "amr": [
    "pwd"
  ]
}

The above end point is looking for those attributes

"identityserver": "owner",
      "fb_product": "owner",
      "fb_order": "owner",
      "fb_payment": "owner",

For running an real application, it is working fine. I am trying to write the test case using micronaut httpclient. But not sure how can I test using micronaut test framework

Something I tried

@Test
@DisplayName("Should create a JWT token")
void shouldCreateAJwtToken() {
    BearerAccessRefreshToken bearerAccessRefreshToken = null;
    UsernamePasswordCredentials creds = new UsernamePasswordCredentials("sherlock", "password");
    HttpRequest request = HttpRequest.POST("/login", creds);
    HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken.class);
    bearerAccessRefreshToken = rsp.body();

}

It creates a token but doesn't contain all the validation required for the above access token.

How do we do rest endpoint unit testing that is protected by JWT token in micronaut

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("sherlock") && authenticationRequest.getSecret().equals("password")) {
                emitter.next(AuthenticationResponse.success((String) authenticationRequest.getIdentity(), List.of("roles1", "roles2")));
                emitter.complete();
            } else {
                emitter.error(AuthenticationResponse.exception());
            }
        }, FluxSink.OverflowStrategy.ERROR);
    }
}
San Jaisy
  • 15,327
  • 34
  • 171
  • 290
  • Do you have a simple project we can clone that shows the issue? – tim_yates Jan 07 '22 at 15:32
  • Its not an issue, I don't know how to test the end point that is been protected by JWT security. Just I want to do the JUnit test using micronaut – San Jaisy Jan 07 '22 at 15:38
  • Does this help? https://github.com/micronaut-projects/micronaut-security/blob/eda614a641aa284f67ed5dc9678774d4f3d8b2e6/security-jwt/src/test/groovy/io/micronaut/security/token/jwt/endpoints/LoginControllerSpec.groovy#L34 – tim_yates Jan 07 '22 at 16:05
  • @tim_yates The above sample creates the JWT and check for the login controller. How do we check the rest end point that has certain claims or not. if that claim exists then only it should pass – San Jaisy Jan 08 '22 at 13:26
  • This solve my issue https://stackoverflow.com/questions/71493722/how-to-pass-the-value-to-the-authenticationprovider-from-httprequest-in-micronau/71551451#71551451 – San Jaisy Mar 22 '22 at 10:43

1 Answers1

0

Create a class in the test source folder that implements AuthenticationProvider and returns the mock authentication. That authentication object can be populated with whatever attributes you want.

James Kleeh
  • 12,094
  • 5
  • 34
  • 61