javajakarta-eecorskeycloak

keycloak-jakarta-servlet-filter-adapter not setting CORS on failed authentication response


My Jakarta web application is using keycloak-jakarta-servlet-filter-adapter to handle authentication from Keycloak.

When the user is authenticated and has the required audience, CORS headers are well set to the preflight and actual responses.

When the user is authenticated but does not have the required audience, the preflight response has CORS headers set, but not the actual response (the one with code 403).

┌───────────────────────────────┐┌─────────────────────────────────┐ 
│ Token with required audience  ││ Token without required audience │ 
│                               ││                                 │ 
│            ┌────────────────┐ ││            ┌────────────────┐   │ 
│ ┌───────┐  │keycloak servlet│ ││ ┌───────┐  │keycloak servlet│   │ 
│ │Browser│  │filter adapter  │ ││ │Browser│  │filter adapter  │   │ 
│ └───┬───┘  └───────┬────────┘ ││ └───┬───┘  └───────┬────────┘   │ 
│     │              │          ││     │              │            │ 
│     │ OPTIONS     ┌┴┐         ││     │ OPTIONS     ┌┴┐           │ 
│     ├────────────►│ │         ││     ├────────────►│ │           │ 
│     │             │ │         ││     │             │ │           │ 
│     │  200 CORS   │ │         ││     │  200 CORS   │ │           │ 
│     │◄────────────┤ │         ││     │◄────────────┤ │           │ 
│     │             └┬┘         ││     │             └┬┘           │ 
│     │ GET         ┌┴┐         ││     │ GET         ┌┴┐           │ 
│     ├────────────►│ │         ││     ├────────────►│ │           │ 
│     │             │ │         ││     │             │ │           │ 
│     │    200 CORS │ │         ││     │ 403 no CORS │ │           │ 
│     │◄────────────┤ │         ││     │◄────────────┤ │           │ 
│     │             └┬┘         ││     │             └┬┘           │ 
└───────────────────────────────┘└─────────────────────────────────┘ 

The problem is my frontend cannot handle the 403 response because the browser (tested on Chrome and Firefox) blocks the response because of the CORS policy, what I understand and accept.

The keycloak filter set the CORS headers either in the method PreAuthActionsHandler.preflightCors() if the request method is "OPTIONS":

at org.keycloak.adapters.PreAuthActionsHandler.preflightCors()
at org.keycloak.adapters.PreAuthActionsHandler.handleRequest()
at org.keycloak.adapters.servlet.KeycloakOIDCFilter.doFilter()

or in the method AuthenticatedActionsHandler.corsRequest() if the token is authenticated :

at org.keycloak.adapters.AuthenticatedActionsHandler.corsRequest()
at org.keycloak.adapters.AuthenticatedActionsHandler.handledRequest()
at org.keycloak.adapters.servlet.KeycloakOIDCFilter.doFilter()

Solution

  • The filter is not the responsible ! It was a Keycloak configuration issue.

    The Keycloak behavior

    When you give the uma_protection role of a client to a user, the roles client scope automatically adds the client to the audience of access token through the audience resolve mapper.

    My bad

    To test the authorization I was removing the uma_protection role of the client from the user. Thus, the client was not added to the audience of the access token anymore.

    The solution

    The goal is to add the client to the audience of the access token.

    Don't know if other options are available, but the one that we chose is to create a client scope with a token mapper of type Audience having the client included :

    The Keycloak form for creating a client scope The Keycloak form for creating a mapper

    We then add this scope to each client that needs an access token allowed to request the first one:

    The Keycloak client scope tab of a client page