Hi, i’m using Keycloak 12.0.3 with OpenID Connect to authenticate our users against some clients (nextcloud, drupal, moodle …).
My goal is to allow only certain groups of users to have access to moodle or nextcloud. For example, students should not have access to nextcloud, but employees should.
I tried to configure keycloak with Authorization policies, but these are effectless. All users within keycloak can authenticate against all clients.
How can I configure keycloak so that only certain groups have access to the respective clients.
Within the script, you can access all user, realm, client and session properties to decide wether authenticate a user or not. The script must be added to the authentication chain after an authenticator which really authenticates the user (User+PW, cookie…).
(If you use an IdP-redirect, it is necessary to add the script to an authentication flow which is configured to be executed as post-idp-flow.)
Instead of stopping the authentication with a standard keycloak error page (as shown in the documentation), it is also possible to trigger a redirect back to the client with a standardised error message code appended to the original redirect_uri query string [1]. Then the “you are not allowed to use this” page is shown by the nextcloud etc, instead of keycloak. But this depends on the capabilities of the client software.
In my case, I checked the values of a user attribute called “memberOf”, which contains AD/LDAP group memberships. The allowed attribute values are configured with regular expressions. Here is the .js code:
AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");
Status = Java.type("javax.ws.rs.core.Response.Status");
URI = Java.type("java.net.URI");
Response = Java.type("javax.ws.rs.core.Response");
function authenticate(context) {
//configuration: example clients with example regexps for matching group attributes
var conf = {
"bbb-client-id": {rx: /^bbb-group-.+$/i, doRedirect: true},
"moodle-client-id": {rx: /^moodle-allowed-group$/i, doRedirect: true},
"nextcloud-client-id": {rx: /^(nextcloud-.+|.+-nextcloud)$/i, doRedirect: false}
};
var client = session.getContext().getClient();
//default: at least one character, ignore case => all groups
var clientId = client.getClientId();
var rx = /^.+$/i;
var doRedirect = false;
if (conf.hasOwnProperty(clientId)) {
rx = conf[clientId].rx;
doRedirect = conf[clientId].doRedirect;
} else {
context.success();
return;
}
//first matching group found, success
for each (var item in user.attributes.memberOf) {
if (rx.test(item)) {
context.success();
return;
}
}
//log events for keycloak admin console
var logevent = context.getEvent()
.user(user)
.detail("username", user.username)
.detail("rx", rx)
.detail("doRedirect", doRedirect);
//depending on the clients capabilities: redirect with standardized oidc method or fail with keycloak error page
if (doRedirect) {
state = authenticationSession.getClientNotes().get("state");
redirect_uri = authenticationSession.getClientNotes().get("redirect_uri");
query = URI.create(redirect_uri).getQuery();
if (query === undefined || query === null || query.trim() === ""){
redirect_uri = redirect_uri + "?";
} else {
redirect_uri = redirect_uri + "&";
}
redirect_uri = redirect_uri + "error=access_denied";
if (state !== undefined && state !== null && state.trim() !== "") {
redirect_uri = redirect_uri + "&state=" + state;
}
//german error text for german browsers
if (user.attributes.locale !== undefined && user.attributes.locale !== null && user.attributes.locale.length > 0 && user.attributes.locale[0].toLowerCase() === "de") {
redirect_uri = redirect_uri + "&error_description=" + encodeURI("Zugriff verweigert für " + user.username);
} else {
redirect_uri = redirect_uri + "&error_description=" + encodeURI("Access denied for " + user.username);
}
logevent.detail("redirect_uri", redirect_uri).error("memberOf_authorization_failed");
context.challenge(Response.seeOther(URI.create(redirect_uri)).build());
} else {
logevent.error("memberOf_authorization_failed");
var formBuilder = context.form();
var form = formBuilder
.setAttribute('client', client)
.setStatus(Status.FORBIDDEN)
.createForm('forbidden.ftl'); //customized error page, standard error.ftl or similar will also work
context.failure(AuthenticationFlowError.INVALID_USER, form);
}
return;
}