Hello Richard,

Yes indeed, that was the problem! It starts just fine on the demo app and
with my keycloak details it authenticates and authorizes as expected.
However, in the real app where I need this, it is giving me 403 (meaning
authentication worked but "admin-role" is somehow still unrecognised)..

So far I developed and used my custom app without Keycloak. I could
delegate the requests to the app-core without issues (because my proxy war
app was already there - within the EAR of that backend system.
But now, when I'm touching the roles, it seems not to recognise my Keycloak
roles ("admin-role", "user-role") and gives me 403.

------ here's what I used to ask ChatGPT about the issue but it wasn't much
of a help ----

My backend app is deployed to TomEE 10 as an EAR. That ear consists of
several WARs, most important ones being:
* app-core.war --> the core of the backend
* app-rest.war --> the rest layer
* my-custom-proxy-app.war --> I added this as proxy layer; external backend
clients (like mobile apps, web apps, desktop apps, etc. send their requests
to this app and it then delegates to the core backend

The backend app itself has its own java/tomcat security layer, consisting
of realms, policies, roles, users, etc.

Now, I want my client apps AND my proxy app (my-custom-proxy-app.war) to
use keycloak for authentication and authorization. On the client apps' end
it is working and the requests are being sent with a valid bearer token.

In TomEE I'm using MP-JWT and it is able to confirm validity of the token
so a request to a protected resource doesn't return 401 (sign that it
authenticated successfully) - but rather it is returning 403, meaning that
the role ("admin-role") that is contained in my Keycloak's token is not
recognized (the role is correctly included under "groups" - it's done on
Keycloak level and is the same keycloak instance I used with the demo app,
where it worked).

Important:
* I don't need my "admin-role" available across other WARs - just in this
my-custom-proxy-app.war
* role in token is correctly configured - I built a demo app where a single
WAR is deployed to TomEE and I get 200 for a protected resource
* everything else, including annotations and MP config is set exactly how
it is set up in the demo app
* I can see backend's roles being defined in
<EAR>/META-INF/application.xml, and then once again in
 <EAR>/app-core/WEB-INF/web.xml -- I tried adding "admin-role" role
definition to both of those places but it changed nothing.

That means, somehow the whole backend application expects my "admin-role"
from the token to be part of backend app's security realm. The thing is: i
don't know how everything works in such a set up.
---------------------------------------------------------------------

I hope this sheds some light on my setup and you will know what the issue
is.


On Fri, 11 Jul 2025 at 20:03, Richard Zowalla <r...@apache.org> wrote:

> Hi,
>
> Had a quick look at your example:
> https://github.com/javac9/tomee-with-jwt-demo
>
> You shouldn’t bundle the API’s in your web app. In TomEE, these libs are
> shipped with the container, i.e
> should be in compileOnly scope:
>
>     implementation("org.apache.tomee:jakartaee-api:10.0")
>
>
> implementation("org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1“)
>
> If you change that and do a full WAR rebuild, the web app will deploy:
>
> tomee   | 11-Jul-2025 18:00:33.859 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints REST
> Application: http://localhost:8080/demoapp/api                ->
> com.example.tomeewithjwtdemo.HelloApplication@5fd8302e
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
> Service URI: http://localhost:8080/demoapp/api/health         -> Pojo
> org.apache.tomee.microprofile.health.MicroProfileHealthChecksEndpoint
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
>    GET http://localhost:8080/demoapp/api/health         ->      Response
> getChecks()
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
>    GET http://localhost:8080/demoapp/api/health/live    ->      Response
> getLiveChecks()
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
>    GET http://localhost:8080/demoapp/api/health/ready   ->      Response
> getReadyChecks()
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
>    GET http://localhost:8080/demoapp/api/health/started ->      Response
> getStartedChecks()
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
> Service URI: http://localhost:8080/demoapp/api/v1             -> Pojo
> com.example.tomeewithjwtdemo.HelloResource
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
>    GET http://localhost:8080/demoapp/api/v1/admin       ->      String
> helloAdmin()
> tomee   | 11-Jul-2025 18:00:33.862 INFO [main]
> org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints
>    GET http://localhost:8080/demoapp/api/v1/user        ->      String
> hello()
> tomee   | 11-Jul-2025 18:00:33.880 INFO [main]
> java.lang.reflect.Method.invoke Deployment of web application archive
> [/usr/local/tomee/webapps/demoapp.war] has finished in [1,420] ms
>
> Since I don’t have a key cloak setup available, it will subsequently fail
> ;-)
>
> Gruß
> Richard
>
>
> > Am 11.07.2025 um 14:54 schrieb Java Entwicklung <javac.ay...@gmail.com>:
> >
> > ```
> > # Tested with:
> > tomee docker image: tomee:plus
> > org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.1
> >
> > # but same is with:
> > tomee docker image: tomee:10.1-jre21-plus
> > org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:2.2-RC1
> > ```
> >
> > Hello,
> >
> > I created a demo Jakarta EE 10 app and configured it so it authenticates
> > incoming requests to my protected resource against my Keycloak instance.
> >
> > It works successfully with Payara Micro 6 but with TomEE 10.0 (which is
> > what I need and have to use) it fails on startup with the following:
> > ```
> > 11-Jul-2025 11:55:42.801 WARNING [main]
> > org.apache.batchee.container.services.ServicesManager.init You didn't
> > specify org.apache.batchee.jmx.application and JMX is already registered,
> > skipping
> > 2025-07-11T11:55:42.804794711Z 11-Jul-2025 11:55:42.804 INFO [main]
> > org.apache.openejb.assembler.classic.Assembler.createApplication Deployed
> > Application(path=/usr/local/tomee/webapps/demoapp)
> > 2025-07-11T11:55:43.426149580Z 11-Jul-2025 11:55:43.425 INFO [main]
> > org.apache.myfaces.webapp.MyFacesContainerInitializer.onStartup Using
> > org.apache.myfaces.webapp.MyFacesContainerInitializer
> > 2025-07-11T11:55:43.614818412Z 11-Jul-2025 11:55:43.614 INFO [main]
> > org.apache.myfaces.webapp.MyFacesContainerInitializer.onStartup Added
> > FacesServlet with mappings=[/faces/*, *.jsf, *.faces, *.xhtml]
> > 2025-07-11T11:55:43.628173897Z 11-Jul-2025 11:55:43.626 SEVERE [main]
> > java.lang.reflect.Method.invoke Error deploying web application archive
> > [/usr/local/tomee/webapps/demoapp.war]
> > 2025-07-11T11:55:43.628209564Z java.lang.IllegalStateException: Error
> > starting child
> > 2025-07-11T11:55:43.628212292Z at
> >
> org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:602)
> > 2025-07-11T11:55:43.628214272Z at
> > org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
> > 2025-07-11T11:55:43.628215708Z at
> > org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
> > 2025-07-11T11:55:43.628217070Z at
> > org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:965)
> > 2025-07-11T11:55:43.628218545Z at
> >
> org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1903)
> > 2025-07-11T11:55:43.628219991Z at
> > java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown
> > Source)
> > 2025-07-11T11:55:43.628221298Z at
> > java.base/java.util.concurrent.FutureTask.run(Unknown Source)
> > 2025-07-11T11:55:43.628222601Z at
> >
> org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
> > 2025-07-11T11:55:43.628224008Z at
> > java.base/java.util.concurrent.AbstractExecutorService.submit(Unknown
> > Source)
> > 2025-07-11T11:55:43.628236311Z at
> > org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
> > 2025-07-11T11:55:43.628238350Z at
> > org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
> > 2025-07-11T11:55:43.628239666Z at
> > org.apache.catalina.startup.HostConfig.start(HostConfig.java:1621)
> > 2025-07-11T11:55:43.628241335Z at
> >
> org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
> > 2025-07-11T11:55:43.628255587Z at
> >
> org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
> > 2025-07-11T11:55:43.628256899Z at
> >
> org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
> > 2025-07-11T11:55:43.628258590Z at
> > org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
> > 2025-07-11T11:55:43.628259958Z at
> >
> org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
> > 2025-07-11T11:55:43.628261506Z at
> >
> org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
> > 2025-07-11T11:55:43.628263034Z at
> > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
> > 2025-07-11T11:55:43.628274187Z at
> >
> org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
> > 2025-07-11T11:55:43.628275693Z at
> >
> org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
> > 2025-07-11T11:55:43.628277030Z at
> > java.base/java.util.concurrent.FutureTask.run(Unknown Source)
> > 2025-07-11T11:55:43.628280369Z at
> >
> org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
> > 2025-07-11T11:55:43.628281839Z at
> > java.base/java.util.concurrent.AbstractExecutorService.submit(Unknown
> > Source)
> > 2025-07-11T11:55:43.628283322Z at
> >
> org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
> > 2025-07-11T11:55:43.628284925Z at
> >
> org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
> > 2025-07-11T11:55:43.628286472Z at
> > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
> > 2025-07-11T11:55:43.628287734Z at
> >
> org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
> > 2025-07-11T11:55:43.628289236Z at
> > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
> > 2025-07-11T11:55:43.628290566Z at
> >
> org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
> > 2025-07-11T11:55:43.628292404Z at
> > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
> > 2025-07-11T11:55:43.628293861Z at
> > org.apache.catalina.startup.Catalina.start(Catalina.java:761)
> > 2025-07-11T11:55:43.628295318Z at
> > java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown
> > Source)
> > 2025-07-11T11:55:43.628301199Z at
> > java.base/java.lang.reflect.Method.invoke(Unknown Source)
> > 2025-07-11T11:55:43.628302697Z at
> > org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
> > 2025-07-11T11:55:43.628304266Z at
> > org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
> > 2025-07-11T11:55:43.628305690Z Caused by:
> > org.apache.catalina.LifecycleException: Failed to start component
> >
> [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/demoapp]]
> > 2025-07-11T11:55:43.628307498Z at
> >
> org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:406)
> > 2025-07-11T11:55:43.628309026Z at
> > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:179)
> > 2025-07-11T11:55:43.628310551Z at
> >
> org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
> > 2025-07-11T11:55:43.628312065Z ... 35 more
> > 2025-07-11T11:55:43.628313380Z Caused by: java.lang.NullPointerException:
> > Cannot invoke "org.eclipse.microprofile.auth.LoginConfig.authMethod()"
> > because "loginConfig" is null
> > 2025-07-11T11:55:43.628315144Z at
> >
> org.apache.tomee.microprofile.jwt.MPJWTInitializer.onStartup(MPJWTInitializer.java:45)
> > 2025-07-11T11:55:43.628316658Z at
> >
> org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4464)
> > 2025-07-11T11:55:43.628320249Z at
> > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
> > 2025-07-11T11:55:43.628321622Z ... 36 more
> > 2025-07-11T11:55:43.632485224Z 11-Jul-2025 11:55:43.632 INFO [main]
> > java.lang.reflect.Method.invoke Deployment of web application archive
> > [/usr/local/tomee/webapps/demoapp.war] has finished in [3,177] ms
> > 2025-07-11T11:55:43.637611803Z 11-Jul-2025 11:55:43.637 INFO [main]
> > java.lang.reflect.Method.invoke Starting ProtocolHandler
> ["http-nio-8080"]
> > 2025-07-11T11:55:43.649430271Z 11-Jul-2025 11:55:43.649 INFO [main]
> > java.lang.reflect.Method.invoke Server startup in [3311] milliseconds
> > ```
> > Here's how I configured it:
> >
> > ```
> > @ApplicationScoped
> > @ApplicationPath("/api")
> > @LoginConfig(authMethod="MP-JWT")
> > @DeclareRoles({
> >        "admin-role",
> >        "user-role"
> > })
> > public class HelloApplication extends Application {
> >
> > }
> >
> > // is a separate class
> > @Path("/v1")
> > @Produces("text/plain")
> > public class HelloResource {
> >
> >    @GET
> >    @Path("/admin")
> >    @RolesAllowed("admin-role")
> >    public String helloAdmin() {
> >        return "Hello, Admin!";
> >    }
> >
> >    @GET
> >    @Path("/user")
> >    public String hello() {
> >        return "Hello, user!";
> >    }
> >
> > }
> > ```
> >
> > In `src/main/resources/META-INF/microprofile-config.properties` I have:
> > ```
> > # JWT Configuration
> > mp.jwt.verify.issuer=http://auth.localhost:8090/realms/myrealm
> > mp.jwt.verify.publickey.location=
> > http://auth.localhost:8090/realms/myrealm/protocol/openid-connect/certs
> > ```
> > and my `src/main/webapp/WEB-INF/web.xml` contains:
> > ```
> > <?xml version="1.0" encoding="UTF-8"?>
> > <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee";
> >         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
> >         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
> > https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd";
> >         version="6.0">
> >
> >
> > </web-app>
> > ```
> >
> > I run gradle war, mount it to my tomee container's deployment dir and
> start
> > it. Again, this exact setup worked perfectly with Payara - the idea was
> to
> > make sure the config is correct before trying to solve startup issue in
> > TomEE (sure, TomEE might require something differently).
> >
> >
> > Note that removing `@LoginConfig(authMethod="MP-JWT")` annotation on the
> > class and using:
> > ```
> > <login-config>
> >  <auth-method>MP-JWT</auth-method>
> > </login-config>
> > ```
> > doesn't have the same effect! There is no such startup error but all
> > resources return `404 Not found`.
> >
> > Also note that I have tested with various other TomEE flavours of version
> > 10 - but no difference.
>
>

Reply via email to