Unable to login to Camunda after upgrading to 7.21

We are using Camunda with Spring boot and trying to migrate Camunda from 7.17.0 to 7.21.0 now (along with Java migration from 17 to 21) in order to get Java 21 support. Along side spring boot migration from 2.7.14 to 3.2.7 (as per Camunda compatibility Spring Boot Version Compatibility | docs.camunda.org).

Dependencies used -
camunda-bpm-spring-boot-starter
camunda-bpm-spring-boot-starter-webapp.

Issue -
Camunda login is enabled using session token from the application logged in user and thus we do not maintain accounts in camunda DB.
{hostname}/camunda/app/welcome/default/#!/login. This works with Camunda 7.17.0.

After migrating to 7.21.0, we are getting below error and thus not able to login to camunda anymore.

Error -
Servlet.service() for servlet [dispatcherServlet] in context with path threw exception
java.lang.NullPointerException: Cannot invoke “org.camunda.bpm.webapp.impl.security.auth.UserAuthentication.getProcessEngineName()” because “authentication” is null
at org.camunda.bpm.webapp.impl.security.auth.Authentications.addOrReplace(Authentications.java:76)
at org.camunda.bpm.webapp.impl.security.auth.ContainerBasedAuthenticationFilter.doFilter(ContainerBasedAuthenticationFilter.java:106)

Previously used -
var authentications = Authentications.getFromSession(request.getSession());
if (authentications != null) {
var authentication = authentications.getAuthenticationForProcessEngine(processEngine.getName());

Currently changed to -
var authentications = AuthenticationUtil.getAuthsFromSession(request.getSession());
if (authentications != null) {
var authentication = authentications.getAuthenticationForProcessEngine(processEngine.getName());

You have to implement Identity Plugin for security reasons now.

The plugin has to fetch the user infor from your login context .

Thank you for your response.

In the shared example, I see UserDetailsService bean defined (in SecurityConfig.java) which configures two users with username and password.
But in our project we are not using camunda login using configured username/ password and do not store these in camunda DB. Rather the authentication happens from the bearer token received (from already existing session) from one of the frontend application and a Filter that does the authentication . What is the correct way to implement the Identity plugin in this scenario.

This was just an example . You have to figure out your solution based on your implementation.

Here is one more example where I implemented it for Camunda with OKTA sso. Here I am fetching user from DB first and if not found then it looks from from spring security context.

Thank you @ad_sahota. After implementing IdentityProviderPlugin this error is gone and I am able to get the user in Authentication object. However, now in authorization, authorization.isGranted() value is being set to false which I can see in SecurityFilter.java. Thus not allowing user to login rather getting 403 error.

Where as in our previous version of code I see the same authorization.isGranted() in SecurityFilter.java is coming true. Do I need to set this value as well or is there any SecurityFilterRule that’s causing the issue in 7.21 version update.

Without looking at your implementation , it is tough to suggest why authorization is failing. You should be looking at following :
How the authorization works in older project? Are you using some camunda java apis to load authorizations/groups from DB . There may be some mechanism like reading user group from application session and setting in AuthenticationResult via setGroups method.
Something like this.

Yes right. Below is our implementation. I have commented the lines as previous version code where we have changed recently.

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.identity.Group;
import org.camunda.bpm.engine.rest.security.auth.AuthenticationProvider;
import org.camunda.bpm.engine.rest.security.auth.AuthenticationResult;
//import org.camunda.bpm.webapp.impl.security.auth.Authentications; //previous version code
import org.camunda.bpm.webapp.impl.security.auth.AuthenticationUtil;
import org.camunda.bpm.webapp.impl.security.auth.UserAuthentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;

//import javax.servlet.http.HttpServletRequest; //previous version code
//import javax.servlet.http.HttpServletResponse; //previous version code
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.security.Principal;
import java.util.*;

import static org.springframework.http.HttpStatus.FORBIDDEN;

@Slf4j
public class CamundaAuthenticationProvider implements AuthenticationProvider {

    private static final String ROLE_PREFIX = "ROLE_";
    private static final String CAMUNDA_GROUP_ID_PREFIX = "camunda-";

    @Override
    public AuthenticationResult extractAuthenticatedUser(HttpServletRequest request, ProcessEngine engine) {
        var principal = request.getUserPrincipal();

        if (principal == null) {
            return AuthenticationResult.unsuccessful();
        }

        var name = principal.getName();
        if (name == null || name.isEmpty()) {
            return AuthenticationResult.unsuccessful();
        }

        var authenticationResult = AuthenticationResult.unsuccessful(principal.getName());

        if (principal instanceof BearerTokenAuthentication) {
            List<String> groupIds = new ArrayList<>();
            List<String> tenantIds = List.of();
            final Collection<GrantedAuthority> authorities = new LinkedHashSet<>(((Authentication) principal).getAuthorities());
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().startsWith(ROLE_PREFIX)) {
                    groupIds.add(mappingAuthorityToGroupId(authority.getAuthority()));
                }
            }
            if (!groupIds.isEmpty()) {
                var validatedGroupIds = validateGroupIds(request, engine, groupIds);
                if (!validatedGroupIds.isEmpty()) {
                    authenticationResult.setAuthenticated(true);
                    authenticationResult.setGroups(validatedGroupIds);
                    authenticationResult.setTenants(tenantIds);
                    logAuthoritiesAndGroups(authorities, validatedGroupIds);
                }
            }
       }

        return authenticationResult;
    }

    private String mappingAuthorityToGroupId(String authority) {
        var role = authority.replace(ROLE_PREFIX, "");
        var camelCaseWords = role.split("(?=[A-Z])");
        return CAMUNDA_GROUP_ID_PREFIX + Arrays.stream(camelCaseWords)
                .map(String::toLowerCase)
                .collect(Collectors.joining("-"));
    }

    @Override
    public void augmentResponseByAuthenticationChallenge(HttpServletResponse response, ProcessEngine engine) {
        response.setStatus(FORBIDDEN.value());
    }

    private void logAuthoritiesAndGroups(Collection<GrantedAuthority> authorities, List<String> groupIds) {
        var message = "Authorities: " + authorities.stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(", "));

        message += "; GroupIds: " + String.join(", ", groupIds);

        log.info(LogFormatter.formatMessage("AUTHORITIES_AND_GROUPS_LOG", message));
    }

    private List<String> validateGroupIds(HttpServletRequest request, ProcessEngine processEngine,
                                          List<String> groupIds) {

        //var authentications = Authentications.getFromSession(request.getSession()); //previous version code
        var authentications = AuthenticationUtil.getAuthsFromSession(request.getSession());
        if (authentications != null) {
            var authentication = authentications.getAuthenticationForProcessEngine(processEngine.getName());
            if (authentication instanceof UserAuthentication userAuthentication) {
                var userAuthenticatedGroups = userAuthentication.getGroupIds();
                if (!userAuthenticatedGroups.isEmpty()
                        && new HashSet<>(groupIds).containsAll(userAuthenticatedGroups)) {
                    return groupIds;
                } else {
					//authentications.removeAuthenticationForProcessEngine(processEngine.getName()); //previous version code
                    authentications.removeByEngineName(processEngine.getName());
                }
            }
        }

        var camundaGroups = processEngine.getIdentityService()
                .createGroupQuery()
                .groupIdIn(groupIds.toArray(String[]::new))
                .list();

        return camundaGroups.stream()
                .map(Group::getId)
                .toList();
    }

}

Try to debug this. Most probably
Either the groups are not present in incoming token OR does not match with groups in your DB.
If this is a new database then your will need a boot user typically configured via
OS variable CAMUNDA_BPM_ADMIN_USER_ID=your-login-id
or config file application.yml

camunda.bpm:
  admin-user:
    id : your-login-id

It will create boot admin and also create a group called camunda-admin in db.
You can then create more groups and authorizations in this database.

I see the groups are present in the token and also it’s same as the required groups to access cockpit. I found the issue most probably in the method AuthenticationUtil.updateCache(). On ContainerBasedAuthenticationFilter.doFilter() → chain.doFilter(request, response), instead of going to SecurityFilter.doFilterSecure(), execution goes to AuthenticationUtil.updateCache() .
where cacheValidationTime is null. Thus, it’s invoking method createAuthentication(String engineName, String username) with groupIds null.

Is there a way to set cacheValidationTime in UserAuthentication object? Please suggest where should that be done. I see cacheValidationTime added to UserAuthentication class in latest version only.

Can you check this documentation

Authentication | docs.camunda.org

and

Process Engine Configuration | docs.camunda.org

These 2 configs may be intersting for you.
camunda.bpm.webapp.auth.cache
.ttl-enabled
.time-to-live

Please ignore if I understood your query wrong and sending you in wrong direction.

Hi @ad_sahota ,

Thank you for the help. Now that I am able to resolve the authentication value and all required authorisations (group, tenants) are present, I don’t get any exception in logs. But the login to Camunda cockpit is still blocked and I see the usual login page of Camunda instead of being logged in to access cockpit.

The logs I am getting now as below and few error that I have highlighted where GET /camunda-welcome gives 404.

Can you please suggest if I am missing anything that I need to add.

2024-08-02 11:12:09,168 DEBUG anwesha [http-nio-8083-exec-7] o.s.s.w.FilterChainProxy Securing GET /camunda/api/engine/engine/
2024-08-02 11:12:09,168 DEBUG anwesha [http-nio-8083-exec-4] o.s.s.w.FilterChainProxy Securing GET /camunda/api/admin/auth/user/default
2024-08-02 11:12:09,169 DEBUG anwesha [http-nio-8083-exec-4] o.s.s.o.s.r.a.JwtAuthenticationProvider Authenticated token
2024-08-02 11:12:09,169 DEBUG anwesha [http-nio-8083-exec-4] o.s.s.o.s.r.w.a.BearerTokenAuthenticationFilter Set SecurityContextHolder to BearerTokenAuthentication [Principal=org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal@686a0ab5, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:x, SessionId=null], Granted Authorities=[SCOPE_internal, SCOPE_public]]
2024-08-02 11:12:09,169 DEBUG anwesha [http-nio-8083-exec-7] o.s.s.o.s.r.w.a.BearerTokenAuthenticationFilter Set SecurityContextHolder to BearerTokenAuthentication [Principal=org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal@11961a52, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:x, SessionId=xxx], Granted Authorities=[SCOPE_internal, SCOPE_public]]
2024-08-02 11:12:09,170 DEBUG anwesha [http-nio-8083-exec-7] o.s.s.w.FilterChainProxy Secured GET /camunda/api/engine/engine/
2024-08-02 11:12:09,170 DEBUG anwesha [http-nio-8083-exec-4] o.s.s.w.FilterChainProxy Secured GET /camunda/api/admin/auth/user/default
2024-08-02 11:12:10,321 DEBUG anwesha [http-nio-8083-exec-5] o.s.w.s.DispatcherServlet GET "/camunda/app/cockpit/locales/en.json?bust=7.21.0", parameters={masked}
2024-08-02 11:12:10,321 DEBUG anwesha [http-nio-8083-exec-8] o.s.w.s.DispatcherServlet GET "/camunda/assets/IBMPlexSans-Italic.woff?bust=7.21.0", parameters={masked}
2024-08-02 11:12:10,322 DEBUG anwesha [http-nio-8083-exec-5] o.s.w.s.h.SimpleUrlHandlerMapping Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/camunda/app/]]
2024-08-02 11:12:10,322 DEBUG anwesha [http-nio-8083-exec-8] o.s.w.s.h.SimpleUrlHandlerMapping Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/camunda/assets/]]
2024-08-02 11:12:10,326 DEBUG anwesha [http-nio-8083-exec-8] o.s.w.s.DispatcherServlet Completed 200 OK
2024-08-02 11:12:10,326 DEBUG anwesha [http-nio-8083-exec-5] o.s.w.s.DispatcherServlet Completed 200 OK
2024-08-02 11:12:10,327 INFO anwesha [http-nio-8083-exec-5] c.d.p.l.f.RequestLogFilter {requestURI=/camunda/app/cockpit/locales/en.json, httpMethod=GET, queryString=bust=7.21.0, responseCode=200}
2024-08-02 11:12:10,327 INFO anwesha [http-nio-8083-exec-8] c.d.p.l.f.RequestLogFilter {requestURI=/camunda/assets/IBMPlexSans-Italic.woff, httpMethod=GET, queryString=bust=7.21.0, responseCode=200}
2024-08-02 11:12:10,528 DEBUG anwesha [http-nio-8083-exec-9] o.s.w.s.DispatcherServlet GET "/camunda/assets/glyphicons-halflings-regular.woff2?bust=7.21.0", parameters={masked}
2024-08-02 11:12:10,528 DEBUG anwesha [http-nio-8083-exec-9] o.s.w.s.h.SimpleUrlHandlerMapping Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/camunda/assets/]]
2024-08-02 11:12:10,531 DEBUG anwesha [http-nio-8083-exec-9] o.s.w.s.DispatcherServlet Completed 200 OK
2024-08-02 11:12:10,531 INFO anwesha [http-nio-8083-exec-9] c.d.p.l.f.RequestLogFilter {requestURI=/camunda/assets/glyphicons-halflings-regular.woff2, httpMethod=GET, queryString=bust=7.21.0, responseCode=200}
2024-08-02 11:12:10,552 INFO anwesha [http-nio-8083-exec-7] c.d.p.l.f.RequestLogFilter {requestURI=/camunda/api/engine/engine/, httpMethod=GET, responseCode=200}
2024-08-02 11:12:10,735 INFO anwesha [http-nio-8083-exec-4] c.d.p.l.f.RequestLogFilter {**requestURI=/camunda/api/admin/auth/user/default, httpMethod=GET, responseCode=404}**
2024-08-02 11:12:10,742 DEBUG anwesha [http-nio-8083-exec-4] o.s.s.w.FilterChainProxy Securing GET /error
2024-08-02 11:12:10,743 DEBUG anwesha [http-nio-8083-exec-4] o.s.s.w.FilterChainProxy Secured GET /error
2024-08-02 11:12:10,743 DEBUG anwesha [http-nio-8083-exec-4] o.s.w.s.DispatcherServlet "ERROR" dispatch for GET "/error", parameters={}
2024-08-02 11:12:10,743 DEBUG anwesha [http-nio-8083-exec-4] o.s.w.s.m.m.a.RequestMappingHandlerMapping Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2024-08-02 11:12:10,758 DEBUG anwesha [http-nio-8083-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor Using 'application/json', given [application/json, text/plain, */*] and supported [application/json, application/*+json]
2024-08-02 11:12:10,758 DEBUG anwesha [http-nio-8083-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor **Writing [{timestamp=Fri Aug 02 11:12:10 UTC 2024, status=404, error=Not Found, path=/camunda/api/admin/auth/u (truncated)...]**
2024-08-02 11:12:10,764 DEBUG anwesha [http-nio-8083-exec-4] o.s.w.s.DispatcherServlet Exiting from "ERROR" dispatch, status 404
2024-08-02 11:12:10,764 INFO anwesha [http-nio-8083-exec-4] c.d.p.l.f.RequestLogFilter {requestURI=/error, httpMethod=GET, responseCode=404}
2024-08-02 11:12:10,783 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.w.FilterChainProxy Securing GET /camunda-welcome
2024-08-02 11:12:10,785 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.o.s.r.a.JwtAuthenticationProvider Authenticated token
2024-08-02 11:12:10,786 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.o.s.r.w.a.BearerTokenAuthenticationFilter Set SecurityContextHolder to BearerTokenAuthentication [Principal=org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal@16de548e, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:x, SessionId=xxx], Granted Authorities=[SCOPE_internal, SCOPE_public]]
2024-08-02 11:12:10,787 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.w.a.s.ChangeSessionIdAuthenticationStrategy Changed session id from xxx
2024-08-02 11:12:10,788 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.w.FilterChainProxy Secured GET /camunda-welcome
2024-08-02 11:12:11,407 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.DispatcherServlet GET "/camunda-welcome", parameters={}
2024-08-02 11:12:11,408 WARN anwesha [http-nio-8083-exec-10] **o.s.w.s.PageNotFound No mapping for GET /camunda-welcome**
2024-08-02 11:12:11,409 WARN anwesha [http-nio-8083-exec-10] **o.s.w.s.PageNotFound No endpoint GET /camunda-welcome.**
2024-08-02 11:12:11,410 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.m.s.DefaultHandlerExceptionResolver Resolved [org.springframework.web.servlet.NoHandlerFoundException: No endpoint GET /camunda-welcome.]
2024-08-02 11:12:11,410 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.DispatcherServlet Completed 404 NOT_FOUND
2024-08-02 11:12:11,411 INFO anwesha [http-nio-8083-exec-10] c.d.p.l.f.RequestLogFilter **{requestURI=/camunda-welcome, httpMethod=GET, responseCode=404}**
2024-08-02 11:12:11,411 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.w.FilterChainProxy Securing GET /error
2024-08-02 11:12:11,412 DEBUG anwesha [http-nio-8083-exec-10] o.s.s.w.FilterChainProxy Secured GET /error
2024-08-02 11:12:11,412 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.DispatcherServlet "ERROR" dispatch for GET "/error", parameters={}
2024-08-02 11:12:11,412 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.m.m.a.RequestMappingHandlerMapping Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2024-08-02 11:12:11,413 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor Using 'application/json', given [application/json, text/plain, */*] and supported [application/json, application/*+json]
2024-08-02 11:12:11,413 DEBUG anwesha [http-nio-8083-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor Writing [{timestamp=Fri Aug 02 11:12:11 UTC 2024, status=404, error=Not Found, path=/camunda-welcome}]

Hello my friends!

Have you run the scripts to change the database tables?

From 7.17 to 7.21, if I’m not mistaken, new columns were added to the tables and these need to be created/updated.

William Robert Alves

Yes added the new columns as below. But getting error GET /camunda-welcome gives 404 (Details in previous note).

Am I missing anything?

Version Table Columns
7.18 ACT_RU_TASK LAST_UPDATED_
7.18 ACT_RU_BATCH START_TIME_
7.18 ACT_RU_BATCH EXEC_START_TIME_
7.18 ACT_HI_BATCH EXEC_START_TIME_
7.21 ACT_RU_EXT_TASK CREATE_TIME_
7.21 ACT_RU_JOB ROOT_PROC_INST_ID_

What URL are you hitting? and what do you see on screen ? screeshot may help.
pom.xml may also help. Also see the browser network tab .
remember now URls are like

https://localhost:8080/camunda/app/welcome
https://localhost:8080/camunda/app/admin

Also remember /camunda-welcome getting 404 is not an issue. This probably checks if you are strating camunda for first time. Even I am getting 404 on a successfully working camunda.

I am using URL - http://localhost:8083/camunda/app/cockpit/default/#/dashboard and it redirects to http://localhost:8083/camunda/app/cockpit/default/#/login after getting error on /camunda-welcome.

I see same error with https://localhost:8083/camunda/app/welcome as well.

And below are the dependecies added in build.gradle .

Hi @ad_sahota ,

Issue appears to be in accessing /camunda/api/admin/auth/user/default which give 404 in updated code. Am I missing any dependency that needs to be added newly.

Below error in updated code getting 404.

In previous version of code getting 200 with below response.

{
“userId”: “xxx”,
“authorizedApps”: [
“admin”,
“tasklist”,
“welcome”,
“cockpit”
]
}

Hi,

Really appreciate any help on the above issue. As I have debugged till all filters are executed, I see the authentication and authorization value. But /camunda/api/admin/auth/user/default still gives 404.

I am also facing same issue after migrating from 7.18 to 7.20 camunda platform.
Please help.

@Anwesha - were you be able to resolve the issue?