OpenID Authentication with Keycloak

Last modified by Andrey Sytchev on 2023/08/24 08:22

The steps below assume, that you have a working Keycloak installation and the clients can connect to XWiki and Keycloak.

You can find a little step by step documentation for mapping ldap groups to keycloak and push them to xwiki.

Follow these steps:

  • Find xwiki.authentication.authclass in xwiki.cfg and comment it out with #-# in the beginning.
  • Add below: xwiki.authentication.authclass=org.xwiki.contrib.oidc.auth.OIDCAuthServiceImpl
  • Open xwiki.properties and adapt the following to your settings, and add this at the end of the file (Note the __XXX__ parts):
    oidc.endpoint.authorization=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/auth
    oidc.endpoint.token=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/token
    oidc.endpoint.userinfo=https://__KEYCLOAK-ADDRESS__/auth/realms/__REALM__/protocol/openid-connect/userinfo
    oidc.scope=openid,profile,email,address
    oidc.endpoint.userinfo.method=GET
    oidc.user.nameFormater=${oidc.user.preferredUsername._clean._lowerCase}
    oidc.user.subjectFormater=${oidc.user.subject}
    # oidc.groups.claim=xwiki_groups
    # oidc.groups.mapping=MyXWikiGroup=my-oidc-group
    # oidc.groups.mapping=MyXWikiGroup2=my-oidc-group2
    # oidc.groups.mapping=MyXWikiGroup2=my-oidc-group3
    # oidc.groups.allowed=
    # oidc.groups.forbidden=
    oidc.userinfoclaims=xwiki_user_accessibility,xwiki_user_company,xwiki_user_displayHiddenDocuments,xwiki_user_editor,xwiki_user_usertype
    # oidc.userinforefreshrate=600000
    oidc.clientid=__KEYCLOAK-CLIENT-ID__
    oidc.secret=__KEYCLOAK-CLIENT-SECRET__
    oidc.endpoint.token.auth_method=client_secret_basic
    oidc.skipped=false
  • Adapt the following to your settings and import it to Keycloak (Again: Note the __XXX__ parts):
    {
    "clientId": "__CLIENT-ID__",
    "name": "__CLIENT-NAME__",
    "rootUrl": "https://__YOUR-WIKI-URL__",
    "adminUrl": "https://__YOUR-WIKI-URL__",
    "baseUrl": "https://__YOUR-WIKI-URL__",
    "surrogateAuthRequired": false,
    "enabled": true,
    "alwaysDisplayInConsole": false,
    "clientAuthenticatorType": "client-secret",
    "redirectUris": [
    "https://__YOUR-WIKI-URL____YOUR_WEBAPP_NAME_OR_EMPTY__/authenticator/callback"
    ],
    "webOrigins": [
    "https://__YOUR-WIKI-URL__"
    ],
    "notBefore": 0,
    "bearerOnly": false,
    "consentRequired": false,
    "standardFlowEnabled": true,
    "implicitFlowEnabled": true,
    "directAccessGrantsEnabled": true,
    "serviceAccountsEnabled": true,
    "authorizationServicesEnabled": true,
    "publicClient": false,
    "frontchannelLogout": false,
    "protocol": "openid-connect",
    "attributes": {
    "saml.assertion.signature": "false",
    "saml.force.post.binding": "false",
    "saml.multivalued.roles": "false",
    "saml.encrypt": "false",
    "saml.server.signature": "false",
    "saml.server.signature.keyinfo.ext": "false",
    "exclude.session.state.from.auth.response": "false",
    "saml_force_name_id_format": "false",
    "saml.client.signature": "false",
    "tls.client.certificate.bound.access.tokens": "false",
    "saml.authnstatement": "false",
    "display.on.consent.screen": "false",
    "saml.onetimeuse.condition": "false"
    },
    "authenticationFlowBindingOverrides": {},
    "fullScopeAllowed": false,
    "nodeReRegistrationTimeout": -1,
    "protocolMappers": [{
    "name": "Client Host",
    "protocol": "openid-connect",
    "protocolMapper": "oidc-usersessionmodel-note-mapper",
    "consentRequired": false,
    "config": {
    "user.session.note": "clientHost",
    "id.token.claim": "true",
    "access.token.claim": "true",
    "claim.name": "clientHost",
    "jsonType.label": "String"
    }
    },
    {
    "name": "Client IP Address",
    "protocol": "openid-connect",
    "protocolMapper": "oidc-usersessionmodel-note-mapper",
    "consentRequired": false,
    "config": {
    "user.session.note": "clientAddress",
    "id.token.claim": "true",
    "access.token.claim": "true",
    "claim.name": "clientAddress",
    "jsonType.label": "String"
    }
    },
    {
    "name": "address",
    "protocol": "openid-connect",
    "protocolMapper": "oidc-address-mapper",
    "consentRequired": false,
    "config": {
    "user.attribute.formatted": "formatted",
    "user.attribute.country": "country",
    "user.attribute.postal_code": "postal_code",
    "userinfo.token.claim": "true",
    "user.attribute.street": "street",
    "id.token.claim": "true",
    "user.attribute.region": "region",
    "access.token.claim": "true",
    "user.attribute.locality": "locality"
    }
    },
    {
    "name": "Client ID",
    "protocol": "openid-connect",
    "protocolMapper": "oidc-usersessionmodel-note-mapper",
    "consentRequired": false,
    "config": {
    "user.session.note": "clientId",
    "id.token.claim": "true",
    "access.token.claim": "true",
    "claim.name": "clientId",
    "jsonType.label": "String"
    }
    }
    ],
    "defaultClientScopes": [
    "web-origins",
    "role_list",
    "roles",
    "profile",
    "email"
    ],
    "optionalClientScopes": [
    "address",
    "phone",
    "offline_access",
    "microprofile-jwt"
    ],
    "access": {
    "view": true,
    "configure": true,
    "manage": true
    }
    }

After importing this to Keycloak, you have to generate a new Client-Secret and put it into xwiki.properties under oidc.secret=__KEYCLOAK-CLIENT-SECRET__.
Aim is: In the end, Client-ID and Client-Secret have to match on Keycloak and in xwiki.properties.

Get Connected