The login with OpenID (Keycloak) in the application “OX AppSuite” does not work

I am trying to find an SSO solution for Keycloak and “OX AppSuite”. When starting “OX AppSuite” via the UCS portal, now I have the following problem:
A new browser window is started. The URL in the address bar changes permanently; the mail browser is not started.

My test system has the following prerequisites in a local network:
Univentionserver UCS 5.0-8.
And the following Apps:
Keycloak 25.0.1-ucs2
OX App Suite 7.10.6-ucs11
OX Connector 2.2.7
Mailserver 12.0
Fetchmail 6.3.26
Domain of the UCS-Server: https://ucs-2700.opa.intranet/
OX App Suite: https://ucs-2700.opa.intranet/appsuite/

The following ClientID was configured with Keycloak: my-openxchange.
See the configuration in my-openxchange.json.

my-openxchange.json

{
“clientId”: “my-openxchange”,
“name”: “openxchange-client”,
“description”: “id für die Konfiguration in openxchange”,
“rootUrl”: “https://ucs-2700.opa.intranet/appsuite/”,
“adminUrl”: “https://ucs-2700.opa.intranet/appsuite/”,
“baseUrl”: “”,
“surrogateAuthRequired”: false,
“enabled”: true,
“alwaysDisplayInConsole”: true,
“clientAuthenticatorType”: “client-secret”,
“secret”: “ty5a5aILHAXthSNDC2P3h9Ga59Kc6zhZ”,
“redirectUris”: [
https://ucs-2700.opa.intranet/appsuite/*
],
“webOrigins”: [
https://ucs-2700.opa.intranet/appsuite/
],
“notBefore”: 0,
“bearerOnly”: false,
“consentRequired”: false,
“standardFlowEnabled”: true,
“implicitFlowEnabled”: false,
“directAccessGrantsEnabled”: false,
“serviceAccountsEnabled”: true,
“authorizationServicesEnabled”: true,
“publicClient”: false,
“frontchannelLogout”: true,
“protocol”: “openid-connect”,
“attributes”: {
“oidc.ciba.grant.enabled”: “false”,
“client.secret.creation.time”: “1729234386”,
“backchannel.logout.session.required”: “true”,
“display.on.consent.screen”: “false”,
“oauth2.device.authorization.grant.enabled”: “false”,
“backchannel.logout.revoke.offline.tokens”: “false”
},
“authenticationFlowBindingOverrides”: {},
“fullScopeAllowed”: true,
“nodeReRegistrationTimeout”: -1,
“protocolMappers”: [
{
“name”: “claim-memberOf”,
“protocol”: “openid-connect”,
“protocolMapper”: “oidc-usermodel-attribute-mapper”,
“consentRequired”: false,
“config”: {
“introspection.token.claim”: “true”,
“userinfo.token.claim”: “true”,
“user.attribute”: “memberOf”,
“id.token.claim”: “true”,
“lightweight.claim”: “true”,
“access.token.claim”: “true”,
“claim.name”: “derMemberOf”,
“jsonType.label”: “String”
}
},
{
“name”: “attribute-scope-matter”,
“protocol”: “openid-connect”,
“protocolMapper”: “oidc-usermodel-attribute-mapper”,
“consentRequired”: false,
“config”: {
“introspection.token.claim”: “true”,
“userinfo.token.claim”: “true”,
“user.attribute”: “uid”,
“id.token.claim”: “true”,
“lightweight.claim”: “false”,
“access.token.claim”: “true”,
“claim.name”: “uid”,
“jsonType.label”: “String”
}
}
],
“defaultClientScopes”: [
“web-origins”,
“acr”,
“roles”,
“email”
],
“optionalClientScopes”: [
“address”,
“read_contacts”,
“phone”,
“offline_access”,
“profile”,
“write_contacts”,
“microprofile-jwt”
],
“access”: {
“view”: true,
“configure”: true,
“manage”: true
}
}

The OpenID Endpoint Configuration: https://ucs-sso-ng.opa.intranet/realms/ucs/.well-known/openid-configuration

{
“issuer”: “https://ucs-sso-ng.opa.intranet/realms/ucs”,
“authorization_endpoint”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/auth”,
“token_endpoint”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/token”,
“introspection_endpoint”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/token/introspect”,
“userinfo_endpoint”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/userinfo”,
“end_session_endpoint”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/logout”,
“frontchannel_logout_session_supported”: true,
“frontchannel_logout_supported”: true,
“jwks_uri”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/certs”,
“check_session_iframe”: “https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/login-status-iframe.html”,
“grant_types_supported”: [
“authorization_code”,
“implicit”,
“refresh_token”,
“password”,
“client_credentials”,
“urn:openid:params:grant-type:ciba”,
“urn:ietf:params:oauth:grant-type:device_code”
],

Below are my customized or newly created configuration files.
/etc/dovecot/conf.d/91-acl_user.conf:

plugin {
 acl_user = %u
}

/opt/open-xchange/etc/as-config.yml:

default:
    host: all
    oidcLogin: true
    oidcPath: /oidc

/opt/open-xchange/etc/oidc.properties (the different variables are set with UCR):

com.openexchange.oidc.enabled=true
com.openexchange.oidc.startDefaultBackend=true
com.openexchange.oidc.hosts=https://ucs-2700.opa.intranet/appsuite/
com.openexchange.oidc.ssoLogout=true
com.openexchange.oidc.clientId=my-openxchange
com.openexchange.oidc.clientSecret=ty5a5aILHAXthSNDC2P3h9Ga59Kc6zhZ
com.openexchange.oidc.rpRedirectURIAuth=https://ucs-2700.opa.intranet/appsuite/
com.openexchange.oidc.rpRedirectURIPostSSOLogout=https://ucs-2700.opa.intranet/appsuite/
com.openexchange.oidc.opIssuer=https://ucs-sso-ng.opa.intranet/realms/ucs
com.openexchange.oidc.opAuthorizationEndpoint=https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/auth
com.openexchange.oidc.opTokenEndpoint=https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/token
com.openexchange.oidc.opJwkSetEndpoint=https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/certs
com.openexchange.oidc.opLogoutEndpoint=https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/logout
com.openexchange.oidc.uiWebPath=https://ucs-2700.opa.intranet/appsuite/
com.openexchange.oidc.failureRedirect=https://ucs-2700.opa.intranet/appsuite/
com.openexchange.oidc.enablePasswordGrant=true
com.openexchange.oidc.passwordGrantUserNamePart=local-part
com.openexchange.oidc.contextLookupClaim=email
com.openexchange.oidc.userLookupClaim=email
com.openexchange.oidc.scope=openid
com.openexchange.oidc.responseType=code
com.openexchange.oidc.contextLookupNamePart=domain
com.openexchange.oidc.userLookupNamePart=local-part
com.openexchange.oidc.oauthRefreshTime=6000

/opt/open-xchange/etc/logback.xml

 ...
  <logger name="com.openexchange.login.internal.LoginPerformer" level="ALL"/>
  <logger name="com.openexchange.sessiond.impl.SessionHandler" level="ALL"/>
...

And here some more UCR-Variables, created according to the specifications of
https://oxpedia.org/wiki/index.php?title=AppSuite:UCS_OIDC_SSO_with_OX_App_Suite

ox/cfg/mailfilter.properties/com.openexchange.mail.filter.masterPassword="@&@/etc/dovecot-master.secret@&@" 
ox/cfg/mail.properties/com.openexchange.mail.masterPassword="@&@/etc/dovecot-master.secret@&@"
ox/cfg/mailfilter.properties/com.openexchange.mail.filter.loginType='global' 
ox/cfg/mailfilter.properties/com.openexchange.mail.filter.passwordSource='global' 
ox/cfg/mail.properties/com.openexchange.mail.mailServerSource='global' 
ox/cfg/mail.properties/com.openexchange.mail.passwordSource='global' 
ox/cfg/sessiond.properties/com.openexchange.sessiond.autologin='false'

And the master password is in /etc/dovecot/master-users as well as in /etc/dovecot-master.secret.

Below are the individual tokens (from Keycloak) for the user “Administrator”
Generated access token 
{
  "exp": 1730390269,
  "iat": 1730389969,
  "jti": "602a3632-3163-4d15-97c1-952f4c0007ef",
  "iss": "https://ucs-sso-ng.opa.intranet/realms/ucs",
  "aud": "account",
  "sub": "dcc21ae2-2e71-45b9-9a13-4fa974829738",
  "typ": "Bearer",
  "azp": "my-openxchange",
  "session_state": "38d68b66-e7d3-4318-88ee-827186df382b",
  "acr": "1",
  "allowed-origins": [
    "https://ucs-2700.opa.intranet/appsuite/"
  ],
  "realm_access": {
    "roles": [
      "default-roles-ucs",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid email",
  "sid": "38d68b66-e7d3-4318-88ee-827186df382b",
  "uid": "Administrator",
  "email_verified": false,
  "email": "admin@opa.intranet",
  "derMemberOf": "cn=Domain Admins,cn=groups,dc=opa,dc=intranet"
}
Generated ID token 
{
  "exp": 1730390269,
  "iat": 1730389969,
  "auth_time": 0,
  "jti": "14988f12-c5ee-4151-918a-5aa6cffd8bd3",
  "iss": "https://ucs-sso-ng.opa.intranet/realms/ucs",
  "aud": "my-openxchange",
  "sub": "dcc21ae2-2e71-45b9-9a13-4fa974829738",
  "typ": "ID",
  "azp": "my-openxchange",
  "session_state": "8df9d9b4-9c03-4d96-b921-d0d3a7f3ca3c",
  "acr": "1",
  "sid": "8df9d9b4-9c03-4d96-b921-d0d3a7f3ca3c",
  "uid": "Administrator",
  "email_verified": false,
  "email": "admin@opa.intranet",
  "derMemberOf": "cn=Domain Admins,cn=groups,dc=opa,dc=intranet"
}
Generated user info 
{
  "sub": "dcc21ae2-2e71-45b9-9a13-4fa974829738",
  "uid": "Administrator",
  "email_verified": false,
  "email": "admin@opa.intranet",
  "derMemberOf": "cn=Domain Admins,cn=groups,dc=opa,dc=intranet"
}

With the help of a browser development tool I can determine the following (recurring) URLs; here an example:

**manifest:**
https://ucs-2700.opa.intranet/appsuite/api/apps/manifests?action=config&version=7.10.6-20.20221018.014235
Status Code: 200 OK

Payload:
action: config
version: 7.10.6-20.20221018.014235

**login:**
https://ucs-2700.opa.intranet/appsuite/api/login?action=autologin&client=open-xchange-appsuite&rampup=true&rampUpFor=open-xchange-appsuite&version=7.10.6-20
Status Code: 200 OK

Payload:
action: autologin
client: open-xchange-appsuite
rampup: true
rampUpFor: open-xchange-appsuite
version: 7.10.6-20

**init:**
https://ucs-2700.opa.intranet/appsuite/api/oidc/init?flow=login&redirect=true&client=open-xchange-appsuite&version=7.10.6-20
Status Code: 302 Found

Payload:
flow: login
redirect: true
client: open-xchange-appsuite
version: 7.10.6-20

**auth:**
https://ucs-sso-ng.opa.intranet/realms/ucs/protocol/openid-connect/auth?response_type=code&client_id=my-openxchange&redirect_uri=https%3A%2F%2Fucs-2700.opa.intranet%2Fappsuite%2F&scope=openid&state=lR72kuQQpFY-2U-q0xS8XcbTF0E8NsdFBKEi1eEc9m8&nonce=0CzKgpOjd6C2fBCcIQY2B7ph7DK6Kbq3Lp5G7dMZkNM
Status Code: 302 Found

Payload:
response_type: code
client_id: my-openxchange
redirect_uri: https://ucs-2700.opa.intranet/appsuite/
scope: openid
state: lR72kuQQpFY-2U-q0xS8XcbTF0E8NsdFBKEi1eEc9m8
nonce: 0CzKgpOjd6C2fBCcIQY2B7ph7DK6Kbq3Lp5G7dMZkNM

**appsuite:**
https://ucs-2700.opa.intranet/appsuite/?state=lR72kuQQpFY-2U-q0xS8XcbTF0E8NsdFBKEi1eEc9m8&session_state=f42b7d97-99e9-4d6c-890b-093b79ec3dfb&iss=https%3A%2F%2Fucs-sso-ng.opa.intranet%2Frealms%2Fucs&code=e9109663-4c33-4a90-aae2-5b0f5727a6a8.f42b7d97-99e9-4d6c-890b-093b79ec3dfb.0942cbb1-a610-40d7-8f6d-220b113d168c
Status Code: 200 OK

Payload:
state: lR72kuQQpFY-2U-q0xS8XcbTF0E8NsdFBKEi1eEc9m8
session_state: f42b7d97-99e9-4d6c-890b-093b79ec3dfb
iss: https://ucs-sso-ng.opa.intranet/realms/ucs
code: e9109663-4c33-4a90-aae2-5b0f5727a6a8.f42b7d97-99e9-4d6c-890b-093b79ec3dfb.0942cbb1-a610-40d7-8f6d-220b113d168c
....
Than another manifest-URL starts again!

/var/log/open-xchange/open-xchange.log:

2024-10-31T16:49:49,890+0100 DEBUG [OXWorker-0000007] org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:263)
after execute filter. filter=TransportFilter@3fc1b2c context=FilterChainContext [connection=TCPNIOConnection{localSocketAddress={/127.0.0.1:8009}, peerSocketAddress={/127.0.0.1:34692}}, closeable=TCPNIOConnection{localSocketAddress={/127.0.0.1:8009}, peerSocketAddress={/127.0.0.1:34692}}, operation=EVENT, message=null, address=/127.0.0.1:34692] nextAction=org.glassfish.grizzly.filterchain.InvokeAction@3510ff7e
 com.openexchange.grizzly.method=GET
 com.openexchange.grizzly.queryString=flow=login&redirect=true&client=open-xchange-appsuite&version=7.10.6-20
 com.openexchange.grizzly.remoteAddress=192.168.0.206
 com.openexchange.grizzly.remotePort=34692
 com.openexchange.grizzly.requestURI=/ajax/oidc/init
 com.openexchange.grizzly.serverName=ucs-2700.opa.intranet
 com.openexchange.grizzly.servletPath=/ajax/oidc/init
 com.openexchange.grizzly.session=1944230380127667449.APP1
 com.openexchange.grizzly.threadName=OXWorker-0000007
 com.openexchange.grizzly.userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
 com.openexchange.localhost.ipAddress=192.168.0.202
 com.openexchange.localhost.version=7.10.6-Rev22
 com.openexchange.request.trackingId=1768283863-884142011
2024-10-31T16:49:49,890+0100 DEBUG [OXWorker-0000007] org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:263)
after execute filter. filter=OXHttpServerFilter@2ec13c23 context=FilterChainContext [connection=TCPNIOConnection{localSocketAddress={/127.0.0.1:8009}, peerSocketAddress={/127.0.0.1:34692}}, closeable=TCPNIOConnection{localSocketAddress={/127.0.0.1:8009}, peerSocketAddress={/127.0.0.1:34692}}, operation=READ, message=org.glassfish.grizzly.http.server.OXResponse@62f3cd29, address=/127.0.0.1:34692] nextAction=org.glassfish.grizzly.filterchain.StopAction@7236e49
 com.openexchange.grizzly.method=GET
 com.openexchange.grizzly.queryString=flow=login&redirect=true&client=open-xchange-appsuite&version=7.10.6-20
 com.openexchange.grizzly.remoteAddress=192.168.0.206
 com.openexchange.grizzly.remotePort=34692
 com.openexchange.grizzly.requestURI=/ajax/oidc/init
 com.openexchange.grizzly.serverName=ucs-2700.opa.intranet
 com.openexchange.grizzly.servletPath=/ajax/oidc/init
 com.openexchange.grizzly.session=1944230380127667449.APP1
 com.openexchange.grizzly.threadName=OXWorker-0000007
 com.openexchange.grizzly.userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
 com.openexchange.localhost.ipAddress=192.168.0.202
 com.openexchange.localhost.version=7.10.6-Rev22
 com.openexchange.request.trackingId=1768283863-884142011
...

I’ve been dealing with this problem for several weeks and I can’t find a solution.
I have no more ideas!
Can anyone help me?

Hi,
I used the following procedure to configure the App Suite as OIDC RP with Keycloak. (credits: @smeyer)
In my case I did not have to configure the Dovecot-related stuff because I already had a working simpleSAMLphp-integration before and would assume that at least this part in OXpedia still applies.

The client in Keycloak was created by using the graphical Admin-GUI.

  • create a new client, remember its name because its used on the relying party (openxchange in my case)
  • RootURL https://webmail.example.org/ (adjust accordingly)
  • Home URL: https://webmail.example.org/appsuite/
  • Valid redirect URIs: https://webmail.example.org/appsuite/api/oidc/auth
  • Web Origins: “*”
  • “Capability Config”:
    – Client Authentication: On
    – Authorization: On (not sure if needed))
    – enable “Standard Flow” and “Direct access grants”
  • Logout Settings:
    – Front Channel logout: Off
    – Backchannel logout session required: on
    – Backchannel logout revoke offline sessions: on

After saving the client get the “Client Secret” from tha “Passwords” tab.

My /opt/open-xchange/etc/as-config.ymlonly has these active lines:

default:
    host: all
    oidcLogin: true

some variables to be used for the configuration:

Issuer="https://ucs-sso-ng.example.org/realms/ucs"
clientId="openxchange". # see remark above
clientSecret="************************************" # use the von generated by Keycloak
userInfoEndpoint="https://ucs-sso-ng.example.org/realms/ucs/protocol/openid-connect/userinfo"
authEndpoint="https://ucs-sso-ng.example.org/realms/ucs/protocol/openid-connect/auth"
tokenEndpoint="https://ucs-sso-ng.example.org/realms/ucs/protocol/openid-connect/token"
jwkSetEndpoint="https://ucs-sso-ng.example.org/realms/ucs/protocol/openid-connect/certs"

The endpoints can be fetched from Keycloak:

curl https://ucs-sso-ng.example.org/realms/ucs/.well-known/openid-configuration

Finally, configure via UCR. In case /opt/open-xchange/etc/openid.properties does not yet exist, touch it.

ucr set ox/cfg/authplugin.properties/com.openexchange.authentication.ucs.searchFilter='(&(objectClass=oxUserObject)(|(uid=%s)(mailPrimaryAddress=%s)))' \
  ox/cfg/sessiond.properties/com.openexchange.sessiond.autologin=false \
  ox/cfg/openid.properties/com.openexchange.oidc.enabled=true \
  ox/cfg/openid.properties/com.openexchange.oidc.ucs.enabled=true \
  ox/cfg/openid.properties/com.openexchange.oidc.startDefaultBackend=false \
  ox/cfg/openid.properties/com.openexchange.oidc.clientId="${client_id}" \
  ox/cfg/openid.properties/com.openexchange.oidc.clientSecret="${client_secret}" \
  ox/cfg/openid.properties/com.openexchange.oidc.opIssuer="${Issuer}" \
  ox/cfg/openid.properties/com.openexchange.oidc.ucs.userInfoEndpoint="${userInfoEndpoint}" \
  ox/cfg/openid.properties/com.openexchange.oidc.opAuthorizationEndpoint="${authEndpoint}" \
  ox/cfg/openid.properties/com.openexchange.oidc.opTokenEndpoint="${tokenEndpoint}" \
  ox/cfg/openid.properties/com.openexchange.oidc.opJwkSetEndpoint="${jwkSetEndpoint}" \
  ox/cfg/openid.properties/com.openexchange.oidc.jwsAlgorithm=RS256 \
  ox/cfg/openid.properties/com.openexchange.oidc.scope="email;openid;profile;offline_access" \
  ox/cfg/openid.properties/com.openexchange.oidc.userLookupClaim=email \
  ox/cfg/openid.properties/com.openexchange.oidc.rpRedirectURIAuth="${redirectURI}"

hth,
Dirk

Mastodon