Getting Started with Alfresco Identity Service EA (Keycloak)

Blog Post created by gravitonian Employee on Jul 17, 2018


There is a new authentication architecture available in the latest releases of the Alfresco products. More specifically we are talking about the ACS 6.0, APS 1.9, and ADF 2.4 product releases. It is still Early Access (EA) functionality not yet supported, so don’t use this in production. The new authentication architecture centralizes the authentication function and supports Single Sign On (SSO). Users authenticate via a new service called the Alfresco Identity Service.


This means that the applications that we are dealing with, such as ACS, APS, and ADF clients don’t have to deal with login forms and authentication. Once a user is logged into the Alfresco Identity Service they don’t have to login again to access ACS, APS, or any ADF application.


This also applies to logout, which means that once a user is logged out of Alfresco Identity Service they are also automatically logged out of all other applications.


Alfresco Identity Service is implemented on top of JBoss Keycloak, which is both an Identity Provider (IdP) and a token issuer for OAuth 2 tokens. Keycloak deals with authentication, safety password storage, SSO, two factor authentication etc. Keycloak supports protocols such as OpenID Connect and SAML. Keycloak can store the user data in a variety of places, such as LDAP, Active Directory, and RDBMS.


At the moment Alfresco Identity Service is the same thing as JBoss Keycloak.

Source Code for this article

The source code for this article can be found here. Clone this project and it will be easy to follow along with this article.


This article assumes that you have the following software installed:


  • Docker, if you don't head over to their site. Docker is used to run the DPB solution.
  • Node.js and NPM, if you don't have these, then head over to the Node.js site and install. Node and npm is used to run ADF apps, which are Angular based.

Installing and Running Alfresco DBP with Identity Service (Keycloak)

At the moment the Alfresco Identity Service is just an instance of JBoss Keycloak. And these days you can spin up pretty much everything with Docker. So we will build a Docker Compose file that starts up ACS 6.0, APS 1.9, Keycloak 3.4.3, and a shared PostgreSQL database.

If you wanted to have a quick look at keycloak you can kick it off like this:

$ docker run -p “8080:8080” -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=secret jboss/keycloak

This uses a built in H2 database, which we actually don’t want to use as we lose all data as soon as the container is removed...

The Docker Compose file that we will be using looks as follows:


# Using version 2 as 3 does not support resource constraint options (cpu_*, mem_* limits) for non swarm mode in Compose
version: "2"

      image: alfresco/alfresco-content-repository:6.0.0
      mem_limit: 1500m
          CATALINA_OPTS: "
          JAVA_OPTS: "
              -Xms1g -Xmx1g
        - ./data/acs:/usr/local/tomcat/alf_data/
        - ./acs/
        - ./acs/
        - 8082:8080 # Browser port
        - 5005:5005 # Remote Debug
        - solr6
        - postgres:postgres
        - solr6
        - postgres

      image: alfresco/alfresco-share:6.0
      mem_limit: 1g
        - REPO_HOST=content
        - REPO_PORT=8080
        - "CATALINA_OPTS= -Xms500m -Xmx500m"
        - 8081:8080
        - content:content
        - content

      image: alfresco/alfresco-search-services:1.1.1
      mem_limit: 2500m
        #Solr needs to know how to register itself with Alfresco
        - SOLR_ALFRESCO_HOST=content
        - SOLR_ALFRESCO_PORT=8080
        #Alfresco needs to know how to call solr
        - SOLR_SOLR_HOST=solr6
        - SOLR_SOLR_PORT=8983
        #Create the default alfresco and archive cores
        - SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive
        - "SOLR_JAVA_MEM=-Xms2g -Xmx2g"
        - ./data/solr/contentstore:/opt/alfresco-search-services/contentstore/
        - ./data/solr/data:/opt/alfresco-search-services/data/
        - 8083:8983 #Browser port

    image: alfresco/process-services:
      ACTIVITI_DATASOURCE_DRIVER: org.postgresql.Driver
      ACTIVITI_HIBERNATE_DIALECT: org.hibernate.dialect.PostgreSQLDialect
      ACTIVITI_DATASOURCE_URL: 'jdbc:postgresql://postgres:5432/activiti?characterEncoding=UTF-8'
      ACTIVITI_ES_DISCOVERY_HOSTS: elasticsearch:9300
      ACTIVITI_ES_CLUSTER_NAME: elasticsearch
      - ./aps/enterprise-license:/root/.activiti/enterprise-license/:ro
      - ./aps/transform.lic:/usr/share/tomcat/lib/transform.lic
      - ./aps/
      - ./aps/
      - ./aps/
      #- ./aps/
      - ./data/aps:/usr/local/data/
      - 9080:8080
      - elasticsearch:elasticsearch
      - postgres:postgres
      - elasticsearch
      - postgres

    image: elasticsearch:1.7.3

    image: postgres:10.1
    mem_limit: 1500m
      - POSTGRES_PASSWORD=alfresco
      - POSTGRES_USER=alfresco
      - POSTGRES_DB=alfresco
    command: postgres -c max_connections=300 -c log_min_messages=LOG
      - 5432:5432
      - ./data/postgres:/var/lib/postgresql/data
      - ./docker-postgresql-multiple-databases:/docker-entrypoint-initdb.d

    image: jboss/keycloak:3.4.3.Final
      KEYCLOAK_USER: admin
      POSTGRES_PORT_5432_TCP_ADDR: postgres
      POSTGRES_PORT_5432_TCP_PORT: 5432
      POSTGRES_DATABASE: keycloak
      POSTGRES_USER: alfresco
      POSTGRES_PASSWORD: alfresco
      - 8080:8080
      - postgres

#  ldap:
#    image: greggigon/apacheds
#    environment:
#      - BOOTSTRAP_FILE=/bootstrap/demo.ldif
#    restart: always
#    ports:
#      - 10389:10389
#    volumes:
#      - ./data/ldap/data:/data
#      - ./ldap/bootstrap:/bootstrap

The interesting part is at the end of the Docker Compose file where we define a new service called the identity-service (i.e. keycloak). It is configured with the following environment variables:







This is the initial Administrator username that will be created first time keycloak is started. You can use this user to login to keycloak. It will automatically be part of the master realm.



Password for the administrator user.



The host where the PostgreSQL database server is running. In this case it points to the internal docker network hostname postgres (the same as the service name in a Docker Compose file)



The port that the PostgreSQL server is listening on. Note. this is the internal port, if you map an external port to something else, then you still need to use 5432.



The name of the keycloak database in the PostgreSQL server. This database is created the first time PostgreSQL starts via an init script called (see source code)



The user that should be used to login to PostgreSQL and access the keycloak database.



PostgreSQL user password.



This is the external port mapping. The keycloak server will start up on port 8080 in the Docker Compose network and be exposed on port 8080 externally. At the moment we cannot configure a different external port as then we cannot configure Keycloak Auth Server URL properly from for example APS. More on this when we configure APS. Note that Share has to be exposed on another port than 8080, but should be OK in a developer environment.


I have seen other environment variables being suggested when configuring keycloak to use an external PostgreSQL database. But these are the once that worked for me with keycloak version 3.4.3.

Now, there are a lot of volumes configured for the services above in the Docker Compose file. They are supported by the following directories and files in the docker-compose directory (see source code):


├── acs (Directory contains custom configuration for ACS)
│   ├──
│   └──
├── aps (Directory contains custom configuration for APS)
│   ├──
│   ├──
│   ├──
│   ├── enterprise-license
│   │ └── (Put your enterprise APS license here)
│   ├──
│   └── transform.lic
├── data (Directory contains all data produced when running DBP)
│   ├── acs
│   ├── aps
│   ├── ldap
│   ├── postgres
│   └── solr
├── docker-compose.yml
├── docker-postgresql-multiple-databases (creates Activiti and Keycloak DBs)
│   └──
└── ldap
   └── bootstrap
       └── demo.ldif

Before you move on and start the Alfresco Digital Business Platform (DBP) solution via the above Docker Compose file you will have to put an APS Enterprise license into the /docker-compose/aps/enterprise-license directory. You can download a trial license from here, which will get you access to a trial license.


Now it is very easy to start the whole DBP solution including the Identity Service, just step into the docker-compose directory that is part of the source code project that you cloned in the beginning and execute (first time you run this it will take some time as this will download all the Docker images from Docker Hub):


docker-compose mbergljung$ docker-compose up

Creating network "docker-compose_default" with the default driver

Creating docker-compose_solr6_1         ... done

Creating docker-compose_postgres_1      ... done

Creating docker-compose_elasticsearch_1    ... done

Creating docker-compose_identity-service_1 ... done

Creating docker-compose_content_1          ... done

Creating docker-compose_process_1          ... done

Creating docker-compose_share_1            ... done


content_1           | 17-Jul-2018 13:51:05.617 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]

content_1           | 17-Jul-2018 13:51:05.658 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]

content_1           | 17-Jul-2018 13:51:05.668 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 257946 ms


Wait until the ACS server has all started, then the system should most likely be ready. Then verify that it’s all working by accessing all the apps. Start with APS via http://localhost:9080/activiti-app and credentials:



Then try ACS at http://localhost:8081/share with admin/admin:



Now access Alfresco Identity Service (i.e. Keycloak) at http://localhost:8080/auth/admin/master/console logging in using admin/admin:



You should see the following screen with the Master realm info after successful login:



We are now ready to start configuring keycloak for use with ACS, APS, and ADF.

Configuring Alfresco Identity Service (keycloak)

Alfresco Identity Service is built on Keycloak and uses the OAuth 2.0 standard, which means that we have the following roles involved in the authentication flow:


  • Resource Owner: The resource owner is the person or application that owns the data that is to be shared. For instance, the end user accessing Alfresco Content Services (ACS) could be a resource owner. The resource that it owns is its Repository content (i.e. folders and files).
  • Resource Server: The resource server is the server hosting the protected resources. For instance, an ACS server is a resource server. It is also capable of dealing with access tokens in protected resource requests.
  • Client: The client application is the application requesting access to the protected resources on behalf of the resource owner. A client application could be an ADF application requesting access to an end user’s ACS repository content.
  • Authorization Server: The authorization server is the server authorizing the client app to access the protected resources of the resource owner. It issues access tokens to the client. In our case this is the Alfresco Identity Service, which is built on Keycloak.


The access token represent the credentials to access protected resources. So your application (i.e. ADF App) does not need to store the resource owner credentials, but instead just the access token. The whole idea with OAuth2 is to create an authentication and authorization system that is not reliant on a username/password combination being broadcast and to instead rely upon a token exchange mechanism built upon digital signature technology.


The following picture illustrates:



By looking at the flows in the above picture we can see that the client (i.e. the ADF App) needs to know about the location of the authorization server and what redirection URL to use. Each resource server (i.e. ACS and APS) needs to also know the location of the authorization server so it can verify the access token being used to access a protected resource. The authorization server needs to be hooked up to a database or a directory with user credentials data.


It is also common to create a separate security realm for the solution that you are implementing authentication for. There is only the master realm by default with the admin user, and we don’t want to use this realm for our DBP solution’s users, groups, roles etc. The master realm is a place for super admins to create and manage the realms in your system.

Creating a DBP Realm

If you have followed along you should now be able to access the Keycloak Admin Console with your admin account by visiting the following URL: http://localhost:8080/auth/admin. By default, you’ll be inside the Master realm.


OK, but what is a realm, really? It’s just a domain in which you apply specific security policies. The Master realm is the parent of any realm you create. For my purpose, I want to create a new realm, which will be a new security domain specifically for the Alfresco Digital Business Platform (DBP) solution.


On the top left of the Admin Console, click the little arrow next to Master. It’ll appear a drop-down menu where Keycloak shows all your realms and let you select any of them:



Click Add realm and then fill in the name of the new realm, in this case we call it alfresco-dbp (note. avoid space in the realm name as it can cause problems):



Now click the Create button. For the moment you can keep all the default configurations for the realm. We can get a quick summary of the new realm by hitting the following URL: http://localhost:8080/auth/realms/alfresco-dbp :





Here we can see that it will use the OpenID Connect protocol.

Create a DBP Client

The first step in securing a web application with Keycloak is creating a client. But what is a client anyway? As discussed before, a Client is an entity that can request authentication on behave of the end-user (e.g. ADF application). A client basically represents a web application that wants to use Keycloak to authenticate and authorize users.


To create a new client, go to Clients:



Then click the Create button to the right:



Set the Client ID to alfresco-client. We will use one Client for ADF apps, ACS, and APS. We want to use the OpenID Connect (OIDC) protocol, which is selected by default. Click Save to create this new client.


Once you create the client, a new page opens with further fields to configure it:



Make sure you set the following options in this way:


  • Enabled: ON - allows turning on and off the client for requesting authentication.
  • Access Type: public - The Access Type option defines the type of the client. As explained in the documentation, “Public access type is for client-side clients that need to perform a browser login”, that is exactly what we want.
  • Standard Flow Enabled: ON -  is used to activate the Authorization Code Flow as defined in the OpenID Connect (OIDC) standard. It’s the recommended protocol to use for authenticating and authorising browser-based applications. With this flow the Keycloak server returns an authorization code, not an authentication token, to the application. The client then exchanges the code for an access token and a refresh token after the browser is redirected back to the application.
  • Implicit Flow Enabled: ON -  where an access token is sent immediately after successful authentication with Keycloak. This may have better performance than standard flow, as there is no additional request to exchange the code for tokens, but it has implications when the access token expires. Important!: This flow needs to be enabled if you want to test the ACS keycloak configuration from the command line with curl, or from Postman. It is also necessary when using ADF applications.
    • However, sending the access token in the URL fragment can be a security vulnerability. For example, the token could be leaked through web server logs and or browser history...
  • Valid Redirect URIs: http://localhost:* - configure it to the base URI for your Alfresco components, for example:


Click the Save button when finished with the Client configuration.

Create a User to Validate the Environment

To validate our environment (and for APS Admin capabilities), it’s recommended to create a local user in Keycloak. The user has to exist in both ACS and APS, so it would make sense to create an Administrator user. If the user does not exist, then we will not be able to authenticate with it in APS, which requires the user to exist.


Click on the Users menu item to the left:



Then click the Add user button:



Fill in the following fields:


  • Username: admin - this username need to match the Administrator username in ACS (i.e. the user we create here need to exist in ACS)
  • Email: - this email address need to match the username in APS (i.e. the user we create here need to exist in APS)
  • First Name and Last Name: set to whatever you like


Click Save button to add the user to the Alfresco DBP realm.


Go to the Credentials tab and insert a new password (I am using admin as password):



For this blog post I don’t want to require the user to change the password on their first login, so I’ll toggle the Temporary option to OFF. After that, click Reset password.

In a production environment, it would be a good idea enabling this option when you define a password on behalf of some user.

We are now ready to start configuration of the DBP components (i.e. ACS, APS, ADF).

Configure Alfresco Process Services (APS)

We will start with the APS application as it’s UI (i.e. /activiti-app) can participate in OpenID Connect authentication. It will act as the OAuth2 Client.


Open up the docker-compose/aps/ file in the source code project and uncomment the following properties so the Alfresco Identity Service (i.e. keycloak) will be enabled for APS:



The only property that you should have to change is the keycloak.auth-server-url property, see below for explanation. The properties have the following meaning (most of them have not been changed from out-of-the-box configuration):


Property Name





True = turns on SSO via Keycloak. False = APS will not be involved in OAuth2 authentication.



Which keycloak security realm should be used (i.e. where are the users)?



Where is the Keycloak server running? Used to construct two URLs: 1) for the browser redirect to the keycloak server 2) for the APS backend communication with keycloak when verifying access tokens.

Which means that this URL has to work both inside and outside the APS container. It does that by using the Mac hostname (in lowercase) and the external port 8080 (the internal port is the same so works both externally and internally).



If set to all, then it ensures that all communication to and from the Keycloak server is over HTTPS.



The OAuth2 client ID. Each application has a client-id that is used to identify the application. We use the same client ID for all Alfresco clients.



The primary user id in APS is email address. So we use the email field in keycloak user account to authenticate.

This is the OpenID Connection ID Token attribute to populate the UserPrincipal name with. If token attribute is null, then defaults to sub. Possible values are sub, preferred_username, email, name, nickname, given_name, family_name.



If set to true, then the APS Activiti App client will not send credentials for the client to Keycloak.


The secret key for this client if the access type is not set to public.



If true, then APS will refresh token in every request.



This should be set to true if APS serves both a web application (e.g. Activiti App) and web services (e.g. ReST). It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 status code to unauthenticated ReST clients instead as they would not understand a redirect to the login page. Keycloak auto-detects ReST clients based on typical headers like X-Requested-With, SOAPAction or Accept. The default value is false.



Possible values are session and cookie. Default is session, which means that APS stores account info in HTTP Session. Alternative cookie means storage of info in cookie.



This tells the adapter to also support basic authentication.


In this case APS is both an OAuth2 Client and an OAuth2 Resource Server.


Now to test it restart just the APS container as follows:


docker-compose mbergljung$ docker-compose stop process

Stopping docker-compose_process_1 ... done

docker-compose mbergljung$ docker-compose rm process

Going to remove docker-compose_process_1

Are you sure? [yN] y

Removing docker-compose_process_1 ... done

docker-compose mbergljung$ docker-compose up -d --no-deps process

Creating docker-compose_process_1 ... done


Note. you need to fully remove the container for these changes to take effect.


When we now go to http://localhost:9080/activiti-app we will be redirected to the Keycloak server for authentication with a URL looking something like:




The Keycloak login screen should now be visible as follows:



In this case we use the email as username and password admin. Just like we used when creating the user in the alfresco-dbp realm. Clicking the Log in button should successfully log us in and we should see the APS dashboard (and the APS login screen should never be visible):



We can logout from APS by clicking Sign out in the upper right corner menu. That should take us back to the Keycloak login screen.

Configure Alfresco Content Services (ACS)

Now, let’s see if we can configure ACS to use the Alfresco Identity Service (i.e. keycloak).  Open up the docker-compose/acs/ file in the source code project and uncomment the following properties so the Alfresco Identity Service (i.e. keycloak) will be enabled for ACS:



The only property that you should have to change is the identity-service.auth-server-url property, see above in the APS config section for explanation.


Now restart the ACS container as follows:


docker-compose mbergljung$ docker-compose stop content

Stopping docker-compose_content_1 ... done

docker-compose mbergljung$ docker-compose rm content

Going to remove docker-compose_content_1

Are you sure? [yN] y

Removing docker-compose_content_1 ... done

docker-compose mbergljung$ docker-compose up -d --no-deps content

Creating docker-compose_content_1 ... done


There is no ACS web application that we can use to verify that ACS keycloak configuration is correct. So we have to call an ACS ReST API instead to test it.


Request an access token first via curl (or with Postman):


$ curl -d 'client_id=alfresco-client' -d 'username=admin' -d 'password=admin' -d 'grant_type=password' 'http://mbp512-mbergljung-0917.local:8080/auth/realms/alfresco-dbp/protocol/openid-connect/token' | python -m json.tool

 % Total    % Received % Xferd  Average Speed Time    Time Time Current

                                Dload Upload Total Spent Left  Speed

100  2682 100  2607 100  75 19029 547 --:--:-- --:--:-- --:--:-- 19576


   "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxdUE1c3FDd0pqV21iNk14SkxrcmhjR0k0cHQtVDRENk1ZRjZvTmx1SF9jIn0.eyJqdGkiOiJkMDc3MTQ5ZS03ZmE0LTQ0NDUtOGZlMS1kM2ZiNDcyNjUyNjgiLCJleHAiOjE1MzEyMzYzMzgsIm5iZiI6MCwiaWF0IjoxNTMxMjM2MDM4LCJpc3MiOiJodHRwOi8vbWJwNTEyLW1iZXJnbGp1bmctMDkxNy5sb2NhbDo4MDgwL2F1dGgvcmVhbG1zL2FsZnJlc2NvLWRicCIsImF1ZCI6ImFsZnJlc2NvLWNsaWVudCIsInN1YiI6IjQ3MGM3YTkxLWIwMTQtNGRhYi05YmFmLTI5MDYyMWMxMDA1ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFsZnJlc2NvLWNsaWVudCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjU1Y2E5MGVjLWVhNWYtNDhiNy1hZTk5LTFkNDkyOThjMWQyOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiQWxmcmVzY28gQWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBbGZyZXNjbyIsImZhbWlseV9uYW1lIjoiQWRtaW4iLCJlbWFpbCI6ImFkbWluQGFwcC5hY3Rpdml0aS5jb20ifQ.TqXDSzXsbIDWpQUJ12xdezB-SOg3NIu5WK8nOGmR9gt3ijISN0xn4BogKC8k-r7ldMqqDsxNrpoHgFNaop6YYtYeDQLAONBhrL8RvupT2OM_vQRt311bw6WS30Q0975ImA6W7s3-MCiFKJr4mvPipsrxocrDs1274tRxIhTWpxTUtQYPlBhPN32PpVows04wT5lO5_uG5BEcRFdWT8RGamT3UtRbkpsQc8ppx0ZC_oUFHtf5HZCPqXmTb95MfdsMR9OgTC15tEy_Jkqy6neZkHOwqfFrFBacJqzcVqhdHTmXIBDZizvSUHu7e3DSDnYIObVQonlOgFvrySAb8QkXeQ",

   "expires_in": 300,

   "refresh_expires_in": 1800,

   "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxdUE1c3FDd0pqV21iNk14SkxrcmhjR0k0cHQtVDRENk1ZRjZvTmx1SF9jIn0.eyJqdGkiOiI5ZTZjZjQ0Yy0yNGQxLTQ4YzItODllOC0xMWYwNmY0NGUyMDciLCJleHAiOjE1MzEyMzc4MzgsIm5iZiI6MCwiaWF0IjoxNTMxMjM2MDM4LCJpc3MiOiJodHRwOi8vbWJwNTEyLW1iZXJnbGp1bmctMDkxNy5sb2NhbDo4MDgwL2F1dGgvcmVhbG1zL2FsZnJlc2NvLWRicCIsImF1ZCI6ImFsZnJlc2NvLWNsaWVudCIsInN1YiI6IjQ3MGM3YTkxLWIwMTQtNGRhYi05YmFmLTI5MDYyMWMxMDA1ZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhbGZyZXNjby1jbGllbnQiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiI1NWNhOTBlYy1lYTVmLTQ4YjctYWU5OS0xZDQ5Mjk4YzFkMjgiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19fQ.XruKZ_87hISBvu9qRb7tV4h8qC89TKFUGB5KUjdPdBbQ_7_W60Zv__vN7X-MlUoXaOM2tddWjY1oKq7iz_S1eKyDZex_To9GSWr1nEFErQjuZ7F_7bln3cdCC0a9p9zBaZm8PwbOnOg5KvJbZHNx4DY1mfWCsQGjqgj_316jSpb3plnHsKTuOjw5YF4QOhk6-O0Y5Ob7mya3E4bxfTHs0x2V37n3nnNlQ9umxwoVKa-wX9q9A3VWu8YynirIaFp1rlMR4nsSQQDXVJX8kitEofVv_I3MRVWRcv49NuHk9Dfda5WvHgPTluol7S_xT5RKpkrtudPXApznDtKYA86y8Q",

   "token_type": "bearer",

   "not-before-policy": 1531230785,

   "session_state": "55ca90ec-ea5f-48b7-ae99-1d49298c1d28"



Then test an ACS ReST API with the access token:


$ curl http://mbp512-mbergljung-0917.local:8082/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children -H "Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxdUE1c3FDd0pqV21iNk14SkxrcmhjR0k0cHQtVDRENk1ZRjZvTmx1SF9jIn0.eyJqdGkiOiJkMDc3MTQ5ZS03ZmE0LTQ0NDUtOGZlMS1kM2ZiNDcyNjUyNjgiLCJleHAiOjE1MzEyMzYzMzgsIm5iZiI6MCwiaWF0IjoxNTMxMjM2MDM4LCJpc3MiOiJodHRwOi8vbWJwNTEyLW1iZXJnbGp1bmctMDkxNy5sb2NhbDo4MDgwL2F1dGgvcmVhbG1zL2FsZnJlc2NvLWRicCIsImF1ZCI6ImFsZnJlc2NvLWNsaWVudCIsInN1YiI6IjQ3MGM3YTkxLWIwMTQtNGRhYi05YmFmLTI5MDYyMWMxMDA1ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFsZnJlc2NvLWNsaWVudCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjU1Y2E5MGVjLWVhNWYtNDhiNy1hZTk5LTFkNDkyOThjMWQyOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiQWxmcmVzY28gQWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBbGZyZXNjbyIsImZhbWlseV9uYW1lIjoiQWRtaW4iLCJlbWFpbCI6ImFkbWluQGFwcC5hY3Rpdml0aS5jb20ifQ.TqXDSzXsbIDWpQUJ12xdezB-SOg3NIu5WK8nOGmR9gt3ijISN0xn4BogKC8k-r7ldMqqDsxNrpoHgFNaop6YYtYeDQLAONBhrL8RvupT2OM_vQRt311bw6WS30Q0975ImA6W7s3-MCiFKJr4mvPipsrxocrDs1274tRxIhTWpxTUtQYPlBhPN32PpVows04wT5lO5_uG5BEcRFdWT8RGamT3UtRbkpsQc8ppx0ZC_oUFHtf5HZCPqXmTb95MfdsMR9OgTC15tEy_Jkqy6neZkHOwqfFrFBacJqzcVqhdHTmXIBDZizvSUHu7e3DSDnYIObVQonlOgFvrySAb8QkXeQ" | python -m json.tool

 % Total    % Received % Xferd  Average Speed Time    Time Time Current

                                Dload Upload Total Spent Left  Speed

100  2758 100  2758 0   0 12312 0 --:--:-- --:--:-- --:--:-- 12312


   "list": {

       "pagination": {

           "count": 7,

           "hasMoreItems": false,

           "totalItems": 7,

           "skipCount": 0,

           "maxItems": 100


       "entries": [


               "entry": {

                   "createdAt": "2018-07-03T13:19:03.359+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:33.994+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "Data Dictionary",

                   "id": "b9589f54-14da-4c96-b5b2-fdd9830d1911",

                   "nodeType": "cm:folder",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"




               "entry": {

                   "createdAt": "2018-07-03T13:19:03.968+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:03.968+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "Guest Home",

                   "id": "fa557b0e-c517-44dd-8470-5f9a654df40d",

                   "nodeType": "cm:folder",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"




               "entry": {

                   "createdAt": "2018-07-03T13:19:04.116+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:04.116+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "Imap Attachments",

                   "id": "ba05fdb9-4807-48b9-b71b-a227e513dbd9",

                   "nodeType": "cm:folder",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"




               "entry": {

                   "createdAt": "2018-07-03T13:19:04.131+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:04.131+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "IMAP Home",

                   "id": "bc6a70ac-22ff-4d35-b3bf-ddf63c3ecf43",

                   "nodeType": "cm:folder",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"




               "entry": {

                   "createdAt": "2018-07-03T13:19:04.048+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:04.048+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "Shared",

                   "id": "3d1d3ad2-4385-4981-b87b-fd5e9a5a3331",

                   "nodeType": "cm:folder",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"




               "entry": {

                   "createdAt": "2018-07-03T13:19:09.878+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:29.873+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "Sites",

                   "id": "962eeedd-142e-4a83-9bcf-32f1f7f0c7ad",

                   "nodeType": "st:sites",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"




               "entry": {

                   "createdAt": "2018-07-03T13:19:04.037+0000",

                   "isFolder": true,

                   "isFile": false,

                   "createdByUser": {

                       "id": "System",

                       "displayName": "System"


                   "modifiedAt": "2018-07-03T13:19:04.037+0000",

                   "modifiedByUser": {

                       "id": "System",

                       "displayName": "System"


                   "name": "User Homes",

                   "id": "0c9744fc-ffd6-4583-aa8d-c8ebebcbcc56",

                   "nodeType": "cm:folder",

                   "parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"







Everything is working fine, time to move on to the ADF applications and configure them to integrate with keycloak.

Configure ADF Applications

To have an ADF application to work with I’m going to use the ADF Project generator and generate an ADP application that will have both process and content components. This way I can test that SSO login works seamlessly for both ACS and APS.


I already got node and npm installed.


Make sure you have newest generator as the keycloak integration only works with ADF version 2.4.0:


$ sudo npm uninstall -g generator-alfresco-adf-app

$ npm install -g generator-alfresco-adf-app


Now, generate an ADF application:


$ yo alfresco-adf-app



   |    | ╭──────────────────────────────────────────╮

   |--(o)--|    │ Update available: 2.0.3 (current: 2.0.0) │

  `---------´   │ Run npm install -g yo to update.     │

   ( _´U`_ )    ╰──────────────────────────────────────────╯

   /___A___\   /

    | ~ |     


´   ` |° ´ Y `



        ,.**. `*****  <-_

       ******** ***** ####   

      $********::**** ####;  

      _.-._`***::*** ######  

    ,*******, *::* .;##### @

    **********,' -=#####',@@@

    ***' .,---, ,.-==@@@@@@@@

     * /@@@@@',@ @\ '@@@@@@@

      '@@@@/ @@@ @@@\ ':#'   

      !@@@@ @@@@ @@@@@@@@@^  

       @@@@ @@@@@ @@@@@@@'   

        `"$ '@@@@@. '##'     



    ADF Angular app generator for Alfresco

          Version 2.4.0


? Your project name adf-app-client

? Application blueprint Process and Content Services

? Would you like to install dependencies now? No


  create .angular-cli.json

  create .editorconfig

  create .npmignore

  create .travis.yml


  create Dockerfile

  create e2e/app.e2e-spec.ts

  create e2e/app.po.ts

  create e2e/tsconfig.e2e.json

  create karma.conf.js

  create LICENSE

  create nginx.conf

  create package.json

  create protractor-ci.conf.js

  create protractor.conf.js

  create proxy.conf.json


  create src/app.config.json

  create src/app/adf.module.ts

  create src/app/app-layout/app-layout.component.css

  create src/app/app-layout/app-layout.component.html

  create src/app/app-layout/app-layout.component.spec.ts

  create src/app/app-layout/app-layout.component.ts

  create src/app/app.component.html

  create src/app/app.component.scss

  create src/app/app.component.spec.ts

  create src/app/app.component.ts

  create src/app/app.module.ts

  create src/app/app.routes.ts

  create src/app/apps/apps.component.css

  create src/app/apps/apps.component.html

  create src/app/apps/apps.component.ts

  create src/app/documentlist/documentlist.component.css

  create src/app/documentlist/documentlist.component.html

  create src/app/documentlist/documentlist.component.spec.ts

  create src/app/documentlist/documentlist.component.ts

  create src/app/file-view/blob-view.component.ts

  create src/app/file-view/bob-view.component.html

  create src/app/file-view/file-view.component.html

  create src/app/file-view/file-view.component.scss

  create src/app/file-view/file-view.component.ts

  create src/app/home/home.component.css

  create src/app/home/home.component.html

  create src/app/home/home.component.spec.ts

  create src/app/home/home.component.ts

  create src/app/login/login.component.css

  create src/app/login/login.component.html

  create src/app/login/login.component.spec.ts

  create src/app/login/login.component.ts

  create src/app/services/preview.service.ts

  create src/app/start-process/start-process.component.html

  create src/app/start-process/start-process.component.scss

  create src/app/start-process/start-process.component.spec.ts

  create src/app/start-process/start-process.component.ts

  create src/app/stencils.module.ts

  create src/app/task-details/task-details.component.css

  create src/app/task-details/task-details.component.html

  create src/app/task-details/task-details.component.spec.ts

  create src/app/task-details/task-details.component.ts

  create src/app/tasks/tasks.component.css

  create src/app/tasks/tasks.component.html

  create src/app/tasks/tasks.component.spec.ts

  create src/app/tasks/tasks.component.ts

  create src/assets/.gitkeep

  create src/custom-style.scss

  create src/environments/

  create src/environments/environment.ts

  create src/favicon-96x96.png

  create src/index.html

  create src/main.ts

  create src/polyfills.ts

  create src/test.ts

  create src/

  create src/tsconfig.spec.json

  create src/typings.d.ts

  create tsconfig.json

  create tslint.json


The ACS and APS URLs need to be configured in the newly generated ADF app as the default ones don’t match. Open up the proxy.conf.json file and change it to the following:



"/alfresco": {

  "target": "http://localhost:8082",

  "secure": false,

  "changeOrigin": true


"/activiti-app": {

  "target": "http://localhost:9080",

  "secure": false,

  "changeOrigin": true




We also need to do some configuration to enable Identity Service authentication (i.e. Keycloak), open up the src/app.config.json file and configure OAuth2 as follows:



  "$schema": "../node_modules/@alfresco/adf-core/app.config.schema.json",

  "ecmHost": "http://{hostname}{:port}",

  "bpmHost": "http://{hostname}{:port}",

  "providers" : "ALL",

  "authType" :"OAUTH",

  "oauth2": {

    "host": "http://mbp512-mbergljung-0917.local:8080/auth/realms/alfresco-dbp",

    "clientId": "alfresco-client",

    "scope": "openid",

    "secret": "",

    "implicitFlow": true,

    "silentLogin": false,

    "redirectUri": "/",

    "redirectUriLogout": "/logout"


  "application": {

      "name": "Alfresco ADF Appplication"


  "languages": [



The only property that you should have to change is the property, see above in the APS config section for explanation. 


Now, install all packages and then start the ADF App as follows:


$ cd adf-app-client/

adf-app-client mbergljung$ npm install


adf-app-client mbergljung$ npm start


And then access the UI on URL http://localhost:4200. It should redirect you to Keycloak for authentication with a URL looking something like this:




And after successfully logging in (with admin/admin) you should be redirected back to the ADF app as per the redirect_uri in the above URL. You should now see the following ADF app UI:



Now click on both Process Apps and Document List in the left navigation menu, you should not be prompted to login. Test also that you can logout by clicking the bottom icon in the left navigation menu.

Using an External LDAP Server

So far we created the user directly in Keycloak. You will most likely not create and store your users in the keycloak database. Most solutions will use an enterprise LDAP server, such as MS Active Directory or OpenLDAP. We will look at how to use an external LDAP server by installing Apache DS locally and then populate it with a couple of users that we will then use for authentication.

Installing Apache Directory Server

The simplest way to get started is using a Docker Hub image. Update the docker-compose/docker-compose.yml file and uncomment the section with the following service definition at the end:



    image: greggigon/apacheds
      - BOOTSTRAP_FILE=/bootstrap/demo.ldif
    restart: always
      - 10389:10389
      - ./data/ldap/data:/data
      - ./ldap/bootstrap:/bootstrap


Make sure you have cloned the source code project as it has an LDIF file located at docker-compose/ldap/bootstrap/demo.ldif that is used to bootstrap the users.


Restart everything:


docker-compose $ docker-compose down

docker-compose $ docker-compose up


You should see something like this in the logs:


ldap_1              | Bootstraping Apache DS with Data from /bootstrap/demo.ldif

ldap_1              | adding new entry "ou=People,dc=example,dc=com"

ldap_1              | adding new entry "ou=RealmRoles,dc=example,dc=com"

ldap_1              | adding new entry "ou=FinanceRoles,dc=example,dc=com"

ldap_1              | adding new entry "uid=jbrown,ou=People,dc=example,dc=com"

ldap_1              | adding new entry "uid=bwilson,ou=People,dc=example,dc=com"

ldap_1              | adding new entry "cn=ldap-user,ou=RealmRoles,dc=example,dc=com"

ldap_1              | adding new entry "cn=ldap-admin,ou=RealmRoles,dc=example,dc=com"

ldap_1              | adding new entry "cn=accountant,ou=FinanceRoles,dc=example,dc=com"

Installing Apache Directory Studio

If you want to you can install Apache Directory Studio and have a look at the directory from there:



Configure Keycloak to use the Directory Server

So we now got the directory server running and it is populated with a couple of users. Time to integrate it with Keycloak so we can authenticate with these new users. Keycloak can federate external user databases. Out of the box there's support for LDAP and Active Directory.


Go to the Admin Console in Keycloak via http://localhost:8080/auth/admin/master/console/.


In the realm alfresco-dbp that we created earlier on click on the Users Federation menu option to get you to the User Federation page:



When you get to this page, there is an Add Provider... select box. You should see ldap within this list. Selecting ldap will bring you to the ldap configuration page. We will add here the ApacheDS LDAP settings:



Here are the fields that you need to configure:


  • Console Display Name: ldap-apacheds - good to have a name corresponding to the directory server that we use
  • Priority: 1 - use this directory server first, if you have more directory servers configured
  • Console Display Name: WRITEABLE - Data will be synced back to apacheds when needed (Import Users mean that users will be imported into keycloak from apacheds)
  • Sync Registrations: ON - newly created users will be synced back to apacheds
  • Vendor: Other - apacheds is not in the list
  • Connection URL: ldap://ldap:10389 - the URL to use when talking to apacheds, note that keycloak talks to apacheds inside the docker compose network, so service name is used as hostname. Make sure to click the Test connection button to verify that the connection is OK
  • Users DN: ou=People,dc=example,dc=com - this is where the users are located in the directory
  • Bind DN: uid=admin,ou=system - this is the user that keycloak will use when talking to apacheds
  • Bind Credentials: secret - this is the password that keycloak will use when talking to apacheds. Make sure to click the Test authentication button to verify that the authentication with the admin user works



Out of the box, Keycloak is configured to import only username, email, first and last name, but you are free to configure mappers and add more attributes or delete default ones. It supports password validation via LDAP/AD protocols and different user metadata synchronization modes.


Here is the full list of attributes for our schema:


Configure APS so it knows about the users in the Directory

You will not be able to use the Apache DS users until they also exist (are known to) APS. So we need to configure LDAP sync for users in APS. You can easily test this by trying to login at http://localhost:9080/activiti-app, you will see an error message as follows when trying to login with a directory user such as jbrown/password:



Configure LDAP Sync as follows in docker-compose/aps/, uncomment the properties in the supplied file from the source code project:


# LDAP Connection,ou=system
# LDAP Sync
ldap.synchronization.full.cronExpression=0 0 0 * * ?
# Need to set proper group search base, otherwise we get exceptions


Restart the process container as follows:


docker-compose mbergljung$ docker-compose stop process

Stopping docker-compose_process_1 ... done

docker-compose mbergljung$ docker-compose rm process

Going to remove docker-compose_process_1

Are you sure? [yN] y

Removing docker-compose_process_1 ... done

docker-compose mbergljung$ docker-compose up -d --no-deps process

Creating docker-compose_process_1 ... done


The logs should show the LDAP sync progress:


process_1           | 08:43:40 [pool-4-thread-1] INFO  com.activiti.api.idm.AbstractExternalIdmSourceSyncService  - No initial LDAP sync info found. Executing full synchronization.


Now verify that it works to login with an LDAP user. Go to http://localhost:9080/activiti-app and use jbrown/password.

Configure ACS so it knows about the users in the Directory

In ACS you don’t have to configure LDAP user sync before you can test with a directory user. ACS will create missing users on the fly. However, if you want the first name, last name, email etc to be available in ACS, then you still need to configure LDAP sync as normally done in ACS. We will not do it here though as we just want to test keycloak authentication.

Test ADF App with Directory user

It should now be possible to login with jbrown/password at http://localhost:4200 and access both process and content components. Note. you might have to logout of the previous session first, and then login as jbrown.