External Task Client with Bearer token

Hi,

I’ve secured my Spring-boot camunda instance with the Keycloak, and now when I’m trying to use ExternalTaskHandler with the @ExternalTaskSubscription annotation i’m not able to subscribe to external task topic. Is it possible to add an interceptor where i would be able to add keycloak token?
I’m using camunda-bpm-spring-boot-starter-external-task-client library.

Cheers

Hello @olszewskia ,

there is a mechanism to add an client request interceptor or filter. You can bind this to a token provider and expose it as a bean. The spring boot starter should be able to pick it up then.

If you don‘t find the right interface, please let me know.

Jonathan

I’ve found here the description of how to add interceptor to external task handler. I’ve added my Autorization header with the token like this

@Configuration
public class KeycloakTokenInterceptor implements ClientRequestInterceptor {

    @Override
    public void intercept(ClientRequestContext requestContext) {
        requestContext.addHeader("Authorization","bearer here_is_the_token");
    }
}

but nothing changed and I still get the following exception again and again.

ERROR [TopicSubscriptionManager][] org.camunda.bpm.client.impl.ExternalTaskClientLogger: TASK/CLIENT-03001 Exception while fetching and locking task.
org.camunda.bpm.client.impl.EngineClientException: TASK/CLIENT-02005 Exception while mapping json object to response dto class 'class org.camunda.bpm.client.impl.EngineRestExceptionDto'
	at org.camunda.bpm.client.impl.EngineClientLogger.exceptionWhileMappingJsonObject(EngineClientLogger.java:50)
	at org.camunda.bpm.client.impl.RequestExecutor.deserializeResponse(RequestExecutor.java:159)
	at org.camunda.bpm.client.impl.RequestExecutor$1.handleResponse(RequestExecutor.java:133)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:223)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:165)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:140)
	at org.camunda.bpm.client.impl.RequestExecutor.executeRequest(RequestExecutor.java:88)
	at org.camunda.bpm.client.impl.RequestExecutor.postRequest(RequestExecutor.java:74)
	at org.camunda.bpm.client.impl.EngineClient.fetchAndLock(EngineClient.java:83)
	at org.camunda.bpm.client.topic.impl.TopicSubscriptionManager.fetchAndLock(TopicSubscriptionManager.java:136)
	at org.camunda.bpm.client.topic.impl.TopicSubscriptionManager.acquire(TopicSubscriptionManager.java:102)
	at org.camunda.bpm.client.topic.impl.TopicSubscriptionManager.run(TopicSubscriptionManager.java:88)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
 at [Source: (org.apache.http.impl.io.EmptyInputStream); line: 1, column: 0]
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
	at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4765)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4667)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3666)
	at org.camunda.bpm.client.impl.RequestExecutor.deserializeResponse(RequestExecutor.java:153)
	... 11 common frames omitted
2023-03-03 11:38:38,058 INFO  [main][723B69A0AC974CB48B5304D805AD962A] org.flywaydb.core.internal.logging.slf4j.Slf4jLog: Flyway Community Edition 8.0.3 by Redgate

Do you know what does it means?

Hi @olszewskia,

my guess is that the response is empty.

Maybe your bean is not instantiated or the token is wrong?

Adding some debug log statement in your code or enabling the HTTP traffic log may help.

Hope this helps, Ingo

2 Likes

Hi @Ingo_Richtsmeier,

Let’s clarify the steps I’ve already made:

  1. I’ve created similar example of spring boot camunda engine and external task worker which operates on the same process but without applied secuity. For this case everything works perfectly fine. Thus, all beans are instantiated properly.
  2. The token is long live one and I’ve used it to make REST request to secured camunda, e.g. to get tasks list, and everything worked properly. Thus, token works perfectly fine.
  3. I’ve debuged my interceptor KeycloakTokenInterceptor and it looks like the token was always added to the headers before request was send to camunda engine.
    For sure you’re right that the response is empty, but the question is, why it is empty?
    I’m gonna try other things but if anything will come to your mind please let me know :slight_smile:

Adam

Hello @olszewskia ,

do you have authorization enabled on the target engine?

If yes, please check the authorization of the client id you are using.

Jonathan

1 Like

Hi @jonathan.lukas
Yep, target engine is secured with Keycloak and camunda keycloak plugin. Security works perfectly fine since I’m able to login to camunda cockpit with my keycloak users and to use engine rest api with keycloak token.

Adam

I’ve found something. It looks like the token which is proper to make rest calls to secured API is not proper for external task client which is not in request scope, but no idea why. I’ve got the following logs:

2023-03-03 19:56:36.208 DEBUG 8584 --- [io-8080-exec-10] o.a.coyote.http11.Http11InputBuffer      : Received [POST /engine-rest/external-task/fetchAndLock HTTP/1.1
User-Agent: Camunda External Task Client
Content-Type: application/json
Content-Length: 817
Host: localhost:8080
Connection: Keep-Alive
Cookie: XSRF-TOKEN=d740c7e1-1a81-4878-aa2b-e42b463066ab
Accept-Encoding: gzip,deflate
Authorization: bearer  my_token_is_here

{"workerId":"5CG0119TK62d908ace-0a70-4ba6-b7af-0751150cb121","maxTasks":10,"usePriority":true,"topics":[{"topicName":"setStatus","lockDuration":20000,"variables":null,"localVariables":false,"businessKey":null,"processDefinitionId":null,"processDefinitionIdIn":null,"processDefinitionKey":null,"processDefinitionKeyIn":null,"processDefinitionVersionTag":null,"processVariables":null,"withoutTenantId":false,"tenantIdIn":null,"includeExtensionProperties":false},{"topicName":"sendEmail","lockDuration":20000,"variables":null,"localVariables":false,"businessKey":null,"processDefinitionId":null,"processDefinitionIdIn":null,"processDefinitionKey":null,"processDefinitionKeyIn":null,"processDefinitionVersionTag":null,"processVariables":null,"withoutTenantId":false,"tenantIdIn":null,"includeExtensionProperties":false}]}]
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.a.t.util.http.Rfc6265CookieProcessor   : Cookies: Parsing b[]: XSRF-TOKEN=d740c7e1-1a81-4878-aa2b-e42b463066ab
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler       : adminRequest http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler       : checkCorsPreflight http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.a.c.authenticator.AuthenticatorBase    : Security checking request POST /engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] org.apache.catalina.realm.RealmBase      :   No applicable constraints defined
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.a.c.authenticator.AuthenticatorBase    : Not subject to any constraint
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler        : AuthenticatedActionsValve.invoke http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler        : Origin: null uri: http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler        : cors validation not needed as we are not a secure session or origin header was null: http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler        : Policy enforcement is disabled.
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy        : Securing POST /engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.209 DEBUG 8584 --- [io-8080-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2023-03-03 19:56:36.210 DEBUG 8584 --- [io-8080-exec-10] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2023-03-03 19:56:36.210 DEBUG 8584 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler       : adminRequest http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.210 DEBUG 8584 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler       : checkCorsPreflight http://localhost:8080/engine-rest/external-task/fetchAndLock
2023-03-03 19:56:36.210 DEBUG 8584 --- [io-8080-exec-10] f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
2023-03-03 19:56:36.210 DEBUG 8584 --- [io-8080-exec-10] o.k.a.BearerTokenRequestAuthenticator    : Found [1] values in authorization header, selecting the first value for Bearer.
2023-03-03 19:56:36.210 DEBUG 8584 --- [io-8080-exec-10] o.k.a.BearerTokenRequestAuthenticator    : Verifying access_token
2023-03-03 19:56:36.211 DEBUG 8584 --- [io-8080-exec-10] o.k.a.BearerTokenRequestAuthenticator    : Failed to verify token
2023-03-03 19:56:36.211 DEBUG 8584 --- [io-8080-exec-10] o.k.adapters.RequestAuthenticator        : Bearer FAILED
2023-03-03 19:56:36.211 DEBUG 8584 --- [io-8080-exec-10] f.KeycloakAuthenticationProcessingFilter : Auth outcome: FAILED```

Hello @olszewskia ,

thank you for the details. There are 2 spaces between Bearer and the token, is this also the case in the actual header?

It seems that for some reason, the token is invalid.

Have you tried to read the token using a jwt decrypt tool?

Jonathan

1 Like

Nah, those two spaces occures only in attached logs where I’ve editted the token. Like I’ve mentioned before, the token is totally correct for POSTMAN client to make calls to camunda engine, e.g. for tasks, to deploy process etc. No idea why this token (now added by interceptor to external task handler request) is not proper anymore.

Hello @olszewskia ,

have you tried executing a fetchAndLock from Postman?

Jonathan

1 Like

Hi @jonathan.lukas

It looks like the problem lies on token side. After changing the token several times it started to work properly, but no idea why. Token life times are set to 30 days, so it shouldn’t be expired. Anyways, thank you @Ingo_Richtsmeier and @jonathan.lukas for your help.

1 Like

Hi @olszewskia
I am also looking for the same solution, could you please help with the steps and if possible please post the code here…

Need below details:
application.yml
External bean class
interceptor
tokenGenerator for life times steps… what will have it the token has expired after 30 days ?

Hi @jilanims,

Here is the part of my application.yml responsible for communication with the camunda-engine

camunda:
  bpm:
    client:
      base-url: http://localhost:8080/engine-rest

Here you can find all required informations about keycloak-camunda integration.

Interceptor is already on the top of this topic.

Here you can find the way to configure your external task client.

I hope it will help you.

Adam

@olszewskia thanks for sharing the details.

Camunda External Task Client, you are hard coding the token in the Interceptor, I believe token life time is 30 days. How you are handling the token refresh or expiration?

In my case okta token life time is 30 mins. so thinking about handling the token refresh. Please share your thoughts or solution.

Hello @jilanims,

Presented interceptor is only an example of how to add token to the headers. I’m using spring SecurityContextHolder to get my token. Your security configuration should handle refresh token and access token management. I’m not familiar with Okta, so not much I can help.

Adam

1 Like

Thanks @olszewskia for responding to my message. I appreciate it.

if possible could you please provide SecurityContextHolder custom code where you are generating the token.