Single-Sign-On in Camunda

Thanks Pieter for you input.

Yes what you explained will be more or less what I am looking for in case of Camunda Rest API.

In addition I am also trying to implement SSO for Webapp Tasklist Application.
Here I am plugging in my Custom filter and making the change in the Web.xml to point to my filter.

Within that Filter (Code is as per Filter here). I am trying to call a WebService to authenticate whether User has valid live session and if yes then go ahead with populating the “setKnownPrinicipal” otherwise just return.

Hope this is the right way to do it.

Hi,

I was using Filter and adding filter with “camunda-auth” and ProcessEngineAuthenticationFilter.class for login and I can able to login using Http protocol but I need to use a secured login using Https where Is it possible to login using secured way using token based authentication by spring-security. Please provide feasible solution for this.

Thanks in advance.

(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)

@Pieter_Vincken did you make further progress on this?

I am wondering any progress here

Me too, :sob: , any progress here?

Hi guys,

The approach I described in my previous post worked. We have a working custom authentication platform. We now have a solution using JWTs to authenticate the user and authenticate them in the engine (using the Identity API).

Since we only use the REST API of Camunda, I have no experience towards getting this approach to work with Camunda Tasklist.

Kind regards

Pieter

Hi @Pieter_Vincken,

In your previous post, you mention that the authorization details (group associations etc.) need to exist in Camunda for the AuthenticationProvider implementation to work. And it’s evident from how the HttpBasicAuthenticationProvider is implemented, Camunda uses the identityService’s password verification to create the AuthenticationResult.
So how actually would you suggest the custom authentication should work? I guess during authentication, Camunda relies on the UserAuthentication object as well, doesn’t it?

We too are trying to hook-in a custom authentication provider to the REST APIs, based on OpenId-Connect (using our own Identity Provider), but can’t figure out the following:

  • If we create a custom filter to intercept all calls and populate the UserAuthentication object to be set to the the session object (Authentications), how the internal calls to the identityService implementation would then be handled?

  • If we go by implementing the AuthenticationProvider, we would still need a way to make the users known to the identityService too for authorizations to be handled. No? How would you suggest the identityService should be implemented then?

Any help would be much appreciated.

Thanks in advance.

Hi @Ashutosh,

The authorization details indeed need to exists within Camunda BPM. We actually decode the JWT in the authentication provider and obtain all necessary data from that object (username and groups) and create memberships on the fly if needed.

We do not implement a completely custom filter. We reused the one from the example and changed it to what we needed. Instead of checking the basic authentication details with the data in the Camunda Engine, we decode the JWT from the request and add all authentication details to the engine. We create the user if necessary and add any non-existing memberships. Finally we create a “successfull authentication” (sorry don’t remember the classname and method by heart).

The authorization details would already exist within the engine. This would be provided by the process implementation. Candidate groups, candidate users, etc are used to authorize the users in the engine. We do not implement a custom authorization service. Camunda still authorizes our calls, we only provide a custom authentication.

Hope this was somewhat clear / useful.

Kind regards,

Pieter Vincken

Is there any progress in this topic?

What i would suggest is to move class org.camunda.bpm.engine.rest.security.auth.AuthenticationProvider to any common jar outside engine-rest.
I this case we could develop custom provider (which extends AuthenticationProvider interface) in external jar and override web.xml with custom provider. That’s it!
Could someone do it? Please!

Erki

1 Like

Example about my JWT provider and filter.

First we need to build custom jar and copy it to Camunda lib folder.
Camunda jar contains following files: filter, provider and customized AuthenticationResult.

JwtAuthenticationProvider.java.txt (3.5 KB)
AuthenticationResultWithGroups.java.txt (620 Bytes)
JwtEngineAuthenticationFilter.java.txt (3.8 KB)

After that some other libraries are required too. Copy following libraries to Camunda lib:
camunda-engine-rest-core-7.7.0.jar, jjwt-0.8.0.jar, jackson-databind-2.6.3.jar, jackson-core-2.6.3.jar, jackson-annotations-2.8.9.jar

And finally override Camunda engine-rest.war web.xml with following rows:

  <filter>
    <filter-name>camunda-auth</filter-name>
    <filter-class>
      org.hunt.kriimsilm.camunda.jwt.JwtEngineAuthenticationFilter
    </filter-class>
    <init-param>
      <param-name>authentication-provider</param-name>
      <param-value>org.hunt.kriimsilm.camunda.jwt.JwtAuthenticationProvider</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>camunda-auth</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

And ensure that all required group IDs + permissions are in Camunda “act_ru_authorization” tabel.

Erki

1 Like

Hi all,

I’m currently trying to implement a SSO mechanism that allows me to open camunda tasklist already logged in. I’ve searched for some ways to implement what i’m trying to achieve but i can’t seem to make it work.

My problem is similar to the @Pieter_Vincken, but i want to auto-login at camunda tasklist when redirected from other site.

Can someone give me an advice to make this work? Is it even possible?

Regards,
Carlos Silva

Via this solution I’'ve built CAS SSO with Camunda: https://github.com/camunda-consulting/camunda-webapp-plugins/tree/master/camunda-webapp-plugin-sso-autologin

But i can not auto logout CAS after logout camunda.
Any one could help me

Hi @Pieter_Vincken

Would you be able to make a step by step guide for me? I’m new to camunda and would like to implement custom authentication using auth0.

Thanks
Julian

Hi,
I faced the same problem, did you find a solution?

Hi guys,

There is another SSO example here if you need some inspiration.

Best,
Ben

2 Likes

Here is one that uses Basic Authentication:

package be.aquafin.bpm.custom;

import static org.camunda.bpm.engine.authorization.Permissions.ACCESS;
import static org.camunda.bpm.engine.authorization.Resources.APPLICATION;

import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;

import org.camunda.bpm.BpmPlatform;
import org.camunda.bpm.engine.AuthorizationService;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.identity.Group;
import org.camunda.bpm.engine.identity.Tenant;
import org.camunda.bpm.webapp.impl.security.SecurityActions;
import org.camunda.bpm.webapp.impl.security.SecurityActions.SecurityAction;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;
import org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter;
import org.camunda.bpm.webapp.impl.security.auth.Authentications;
import org.camunda.bpm.webapp.impl.security.auth.UserAuthentication;

import java.util.logging.Logger;

/**

  • Replaces {@link AuthenticationFilter} by an extension which can auto-logon a
  • user by using Basic Authentication

*/
public class LoginAuthenticationFilter implements Filter {

private static final String[] APPS = new String[] { "cockpit", "tasklist" };
private static final String APP_MARK = "/app/";
private static final String CAMUNDA_ADMIN_GROUP = "BPM-ADMIN";

private static final Logger logger = Logger.getLogger(AuthenticationFilter.class.getName());

public void init(FilterConfig arg0) throws ServletException {
}

public void destroy() {
}

public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
		throws IOException, ServletException {
	final HttpServletRequest req = (HttpServletRequest) request;

	// get authentication from session
	Authentications authentications = Authentications.getFromSession(req.getSession());

	// This function is added to the normal AuthenticationFilter
	setAutoLoginAuthentication(request, response, authentications);

	// set current authentication to the one restored from session (maybe
	// auto login was added)
	Authentications.setCurrent(authentications);

	try {
		SecurityActions.runWithAuthentications(new SecurityAction<Void>() {
			public Void execute() {
				try {
					chain.doFilter(request, response);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				return null;
			}
		}, authentications);
	} finally {
		Authentications.clearCurrent();
		// store updated authentication object in session for next request
		Authentications.updateSession(req.getSession(), authentications);
	}
}

/**
 * Reads the auto-login-username from the URL parameters and create an
 * {@link Authentication} for it containing its groups, tenants and
 * authorized apps.
 * 
 */
protected void setAutoLoginAuthentication(final ServletRequest request, ServletResponse response, Authentications authentications) throws ServletException {
	boolean isAdmin = false;
	String username = null;
	final HttpServletRequest req = (HttpServletRequest) request;
	final ProcessEngine engine = getEngine();

	Principal principal = req.getUserPrincipal();
	if (principal != null && principal.getName() != null && !principal.getName().isEmpty()) {
		username = principal.getName();
		logger.info("principal username = " + username);
		username = username.toUpperCase();
	} else {
		String authHeader = req.getHeader("Authorization");
		if (authHeader != null && !authHeader.isEmpty()) {
			String encodedValue = authHeader.split(" ")[1];
			byte[] decoded = DatatypeConverter.parseBase64Binary(encodedValue);
			String decodedString = "";
			try {
				decodedString = new String(decoded, "UTF-8");
			} catch (Exception ex) {
				throw new RuntimeException("Kan basic authentication string niet decoderen", ex);
			}
			username = decodedString.split(":")[0];
			logger.info("basic authentication username = " + username);
			username = username.toUpperCase();
			String password = decodedString.split(":")[1];
			//AUTHENTICATION TO CAMUNDA (AD)
			if (!isAuthenticated(engine, username, password)) {
				HttpServletResponse httpServletResponse = (HttpServletResponse) response;
				try {
				    unauthorized(httpServletResponse, "Foute username of paswoord!");
				} catch (Exception ex) {
					throw new RuntimeException("sending unauthorized response failed"); 
				}
			}
			else {
				logger.info("username " + username+" is authenticated");
			}
		}
	}
	if (username != null && !username.isEmpty()) {
		// if already in the list of logged in users - nothing to do
		Authentication authentication = authentications.getAuthenticationForProcessEngine(engine.getName());
		if (authentication != null) {
			if (authentication.getName().equals(username)) {
				logger.info(" username = " + username+ " is reeds ingelogd");
				return;
			}
		}
		else {
			logger.info("username " + username+" geen authenticatie record gevonden");
		}

		List<String> groupIds = new ArrayList<String>();
		String url = req.getRequestURL().toString();
		logger.info("url = "+url);
		String appname = getApp(url);
		if (appname != null) {
			logger.info("appname " + appname);
			if (engine != null) {
				final List<Group> groupList = engine.getIdentityService().createGroupQuery().groupMember(username)
						.list();
				// transform into array of strings:
				for (Group group : groupList) {
					groupIds.add(group.getId());
					logger.info("group.getId() " + group.getId());
					if (group.getId().equals(CAMUNDA_ADMIN_GROUP)) {
						isAdmin = true;
						logger.info(" username = " + username+ " is in ADMIN group");
					}
				}
			}
			AuthorizationService authorizationService = engine.getAuthorizationService();
			List<String> tenantIds = getTenantsOfUser(engine, username);
			// check user's app authorizations by iterating of
			// list of apps and ask if permitted
			HashSet<String> authorizedApps = new HashSet<String>();
			if (engine.getProcessEngineConfiguration().isAuthorizationEnabled()) {
				for (String application : APPS) {
					if (authorizationService.isUserAuthorized(username, groupIds, ACCESS, APPLICATION,
							application)) {
						logger.info(" username = " + username+ " heeft toegang tot " + application);
						authorizedApps.add(application);
					}
				}
				if (isAdmin)
					authorizedApps.add("admin");
			} else {
				Collections.addAll(authorizedApps, APPS);
				authorizedApps.add("admin");
			}
			if (authorizedApps.contains(appname)) {
				// create new authentication object to store
				// authentication
				UserAuthentication newAuthentication = new UserAuthentication(username, engine.getName());
				newAuthentication.setGroupIds(groupIds);
				newAuthentication.setTenantIds(tenantIds);
				newAuthentication.setAuthorizedApps(authorizedApps);
				// and add the new logged in user
				authentications.addAuthentication(newAuthentication);
			}
		}
		else {
			logger.info("geen app in url?");
		}
	} 
}

protected boolean isAuthenticated(ProcessEngine engine, String userName, String password) {
	return engine.getIdentityService().checkPassword(userName, password);
}

/**
 * copied from
 * org.camunda.bpm.webapp.impl.security.auth.UserAuthenticationResource
 */
protected List<String> getGroupsOfUser(ProcessEngine engine, String userId) {
	List<Group> groups = engine.getIdentityService().createGroupQuery().groupMember(userId).list();
	List<String> groupIds = new ArrayList<String>();
	for (Group group : groups) {
		groupIds.add(group.getId());
	}
	return groupIds;
}

private String getApp(String url) {
	String app = null;
	int index = url.indexOf(APP_MARK);
	if (index >= 0) {
		String urlLastPart = url.substring(index + APP_MARK.length());
		index= urlLastPart.indexOf("/");
		if (index >= 0) {
			app = urlLastPart.substring(0,index);
		}
		else {
			app = urlLastPart;
		}
	}
	return app;
}

protected List<String> getTenantsOfUser(ProcessEngine engine, String userId) {
	List<Tenant> tenants = engine.getIdentityService().createTenantQuery().userMember(userId)
			.includingGroupsOfUser(true).list();

	List<String> tenantIds = new ArrayList<String>();
	for (Tenant tenant : tenants) {
		tenantIds.add(tenant.getId());
	}
	return tenantIds;
}

private ProcessEngine getEngine() {
	return BpmPlatform.getDefaultProcessEngine();
}

private void unauthorized(HttpServletResponse response, String message) throws IOException {
    response.setHeader("WWW-Authenticate", "Basic realm=Protected");
    response.sendError(401, message);
  }

  private void unauthorized(HttpServletResponse response) throws IOException {
    unauthorized(response, "Unauthorized");
  }

Hi @speetrs , we had the same piece of code for the already sso’ed session. But we get
below error :
unable to create session after the response has been commited

With this approach one can only access Rest api’s of engine by passing jwt token. But How one can login to Camunda Web apps using jwt ?