0

I am relatively new to Java programming so please bear with me. I am developing a microservice to be deployed on Payara Micro. For security, I am using JWTs. Because Payara Micro is an implementation of MicroProfile, I am trying to leverage the MicroProfile JWT auth spec (Payara Micro implements spec 1.1). In accordance with the spec, I am annotating the JAX-RS configuration class with @LoginConfig:

@ApplicationScoped
@LoginConfig(authMethod = "MP-JWT")
@DeclareRoles({"admin", "standard", "premium"})
@ApplicationPath("/*")
public class IdentityService extends Application {}

Payara Micro implements a filter that activates @RolesAllowed annotations on JAX-RS resources (normally exclusively available to EJBs). I am leveraging this annotation to identify the claims of the JWT using the JsonWebToken interface from the MP JWT auth spec to determine if the user is allowed to make a GET request to access a user's information based on the id in their claim:

@RolesAllowed({"admin", "standard", "premium"})
@RequestScoped
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
public class UserEndpoint {

    @Inject
    private JsonWebToken jwtPrincipal;

    @Inject
    private UserRepository userRepo;

    @Path("/{id}")
    @GET
    public Response get(@PathParam("id") String id) {
        if (!jwtPrincipal.getSubject().equalsIgnoreCase(id))
            return Response.status(Response.Status.FORBIDDEN).build();
        User user;
        user = userRepo.get(UUID.fromString(id));
        return Response.status(Response.Status.OK).entity(user).build();
    }

Additionally, according to Payara's MP JWT auth activation guide, I am providing the following config properties in my META-INF/microprofile-config.properties file for Payara to verify:

mp.jwt.verify.issuer = com.someDomain
mp.jwt.verify.publickey.location = jwtPublic.pem

It should be noted that I am vending my own JWTs through a separarate @PermitAll endpoint based on user login information. However, these seem to be correctly constructed as they can easily be debugged and signture is verified in jwt.io's debugger (not providing keys for verification because question is already long enough):

ew0KICAidHlwIiA6ICJKV1QiLA0KICAiYWxnIiA6ICJSUzI1NiINCn0=.ew0KICAiaXNzIiA6ICJjb20uc29tZURvbWFpbiIsDQogICJzdWIiIDogIjA5M2EzYzRlLWRiZTItNGI2YS05NzU4LTI5YjM2NjU1MTljOSIsDQogICJleHAiIDogMTU2NTM1Mjc2OCwNCiAgImlhdCIgOiAxNTY1MzA5NTY4LA0KICAidXBuIiA6ICJzb21lRW1haWxAZW1haWwuY29tIiwNCiAgImp0aSIgOiAiYmU0OTNhMTgtOGZiOS00NmM3LTgwMmEtNDY1YjFhYmM2YmZlIiwNCiAgImdyb3VwcyIgOiBbICJzdGFuZGFyZCIgXSwNCiAgInByZWZlcnJlZF91c2VybmFtZSIgOiAiY2FtZXJvbjIiDQp9.F97KMZbkPNuMATlYm0pLKalvadf_aAoxgPiNR-V-Y8toeYNqAm-w1WwnttQCeBuWvXjvPCm70y-HDGGv2tB-KuzHFxKxreyDue11db1fQ6o7QtYWSYvu_1y8bhOnB-MUz3MWnhRSHj8GpCQkrYXNACW9VSv8poqgyhyctEhi98LHGCFLyg1JNrzZw2J7fCdOYeoVZlgo-I9F4XA7FIXSQxgUii1T5RcGkky3tBKVUZ8jIigW68LXtYlq12lfo3PDATxou7c5ybMijLYuwPi7A6rb5bvsqAdsO-mSP6M6t42mUYGJuJ9dx_GmhWOpYzfgFaynMHElpuV58q7pXP5_JRvMx37yHMuA0Z_dj9ruSBqqH-lN0gV3CDheuHICbxAwqFvCEgVG7ZC4S3JNkSTqofifw_iXfTJX-v8cfWI3kfH7PrZmSmrMApGQLt5bQw_HIcIWfbHuA9_r-YksdIzJRsW-2JpSEHxRPeGvq5BlXlMSWu4BoefGcTbj6OKj6Gz_Zb5O8l8mOxQAT3wElN3DRxj3M_jxRM6kg-C-DlEFAxFivNQGbpE4CSKnLaY8XnTeL3j3Pq7tnYm1kG4PWeHCppr9CKgITjfrzzY3UNQX56WwDuRFTfgmY2Tnji4xqmLxxVCGE8ahM8mnxouIiwlPjlixkRXJfkxYb8YaQSMjIGw=

However, the behavior of the @RolesAllowed annotation seems to be unexpected. When I send a curl to the endpoint while passing the JWT bearer token as an authorization HTTP header:

curl -i --header "Authorization: Bearer ew0KICAidHlwIiA6ICJKV1QiLA0KICAiYWxnIiA6ICJSUzI1NiINCn0=.ew0KICAiaXNzIiA6ICJjb20uc29tZURvbWFpbiIsDQogICJzdWIiIDogIjA5M2EzYzRlLWRiZTItNGI2YS05NzU4LTI5YjM2NjU1MTljOSIsDQogICJleHAiIDogMTU2NTM1Mjc2OCwNCiAgImlhdCIgOiAxNTY1MzA5NTY4LA0KICAidXBuIiA6ICJzb21lRW1haWxAZW1haWwuY29tIiwNCiAgImp0aSIgOiAiYmU0OTNhMTgtOGZiOS00NmM3LTgwMmEtNDY1YjFhYmM2YmZlIiwNCiAgImdyb3VwcyIgOiBbICJzdGFuZGFyZCIgXSwNCiAgInByZWZlcnJlZF91c2VybmFtZSIgOiAiY2FtZXJvbjIiDQp9.F97KMZbkPNuMATlYm0pLKalvadf_aAoxgPiNR-V-Y8toeYNqAm-w1WwnttQCeBuWvXjvPCm70y-HDGGv2tB-KuzHFxKxreyDue11db1fQ6o7QtYWSYvu_1y8bhOnB-MUz3MWnhRSHj8GpCQkrYXNACW9VSv8poqgyhyctEhi98LHGCFLyg1JNrzZw2J7fCdOYeoVZlgo-I9F4XA7FIXSQxgUii1T5RcGkky3tBKVUZ8jIigW68LXtYlq12lfo3PDATxou7c5ybMijLYuwPi7A6rb5bvsqAdsO-mSP6M6t42mUYGJuJ9dx_GmhWOpYzfgFaynMHElpuV58q7pXP5_JRvMx37yHMuA0Z_dj9ruSBqqH-lN0gV3CDheuHICbxAwqFvCEgVG7ZC4S3JNkSTqofifw_iXfTJX-v8cfWI3kfH7PrZmSmrMApGQLt5bQw_HIcIWfbHuA9_r-YksdIzJRsW-2JpSEHxRPeGvq5BlXlMSWu4BoefGcTbj6OKj6Gz_Zb5O8l8mOxQAT3wElN3DRxj3M_jxRM6kg-C-DlEFAxFivNQGbpE4CSKnLaY8XnTeL3j3Pq7tnYm1kG4PWeHCppr9CKgITjfrzzY3UNQX56WwDuRFTfgmY2Tnji4xqmLxxVCGE8ahM8mnxouIiwlPjlixkRXJfkxYb8YaQSMjIGw=" "localhost:8080/users/093a3c4e-dbe2-4b6a-9758-29b3665519c9"

I receive the following response:

HTTP/1.1 401 Unauthorized
Server: Payara Micro #badassfish
WWW-Authenticate: Authentication resulted in SEND_FAILURE

After digging around in Payara's source code, I discovered that this response is received here.

Apparently, this occurs when the Principal received from SecurityContext is null and when a @RolesAllowed annotation is present in a JAX-RS resource. The container-provided authentication module is then apparently called, which in Payara's case is BASIC authentication: https://github.com/payara/Payara/issues/2326#issuecomment-367855133. However, I don't understand why BASIC authentication is being used when I specify @LoginConfig(authMethod="MP-JWT") and I am providing a JWT as an authorization header. There are issues related to this on github but I haven't been able to find a definitive answer on what the fix is.

So, my questions are, am I incorrectly interpreting how to get Payara to produce an injectable JsonWebToken of the caller? How can I fix this SEND_FAILURE response?

Cam
  • 11
  • 3

1 Answers1

1

For anyone who may face this issue, after taking a look at the source code for Payara's SignedJWTIdentityStore, I realized that the META-INF/microprofile-config.properties file isn't scanned by Payara to verify the issuer and public key. While Payara's guide to activating MP JWT Auth doesn't make this explicitly clear, you can't configure the issuer or public key location from this file. To configure your accepted issuer, you MUST place a file called payara-mp-jwt.properties with key accepted.issuer and expected value (e.g. accepted.issuer = someDomain.com) at the src/main/resources directory. Additionally, to configure your public key, you MUST place your public key called publicKey.pem at the same directory. Payara will only scan for these files and won't scan META-INF/microprofile.config for your configured public key and accepted issuer values:

public SignedJWTIdentityStore() {
        config = ConfigProvider.getConfig();

        Optional<Properties> properties = readVendorProperties();
        acceptedIssuer = readVendorIssuer(properties)
                .orElseGet(() -> config.getOptionalValue(ISSUER, String.class)
                .orElseThrow(() -> new IllegalStateException("No issuer found")));

        jwtTokenParser = new JwtTokenParser(readEnabledNamespace(properties), readCustomNamespace(properties));
    }

private Optional<Properties> readVendorProperties() {
        URL mpJwtResource = currentThread().getContextClassLoader().getResource("/payara-mp-jwt.properties");
        Properties properties = null;
        if (mpJwtResource != null) {
            try {
                properties = new Properties();
                properties.load(mpJwtResource.openStream());
            } catch (IOException e) {
                throw new IllegalStateException("Failed to load Vendor properties from resource file", e);
            }
        }
        return Optional.ofNullable(properties);
    }

Including these will cause the JsonWebToken to be correctly injected. I'm not sure why Payara's doc bothered to include the other file configuration option if it is not scanned.

Cam
  • 11
  • 3