OpenID Authentication with Keycloak

Last modified by Thomas Klosinsky on 2025/06/10 13:55

Information

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

Information

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
    	}
    }
Warning

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