User attribute based web service access control by Keycloak
Asked Answered
P

3

0

I've spent a lot of time trying to find a way to limit the access for web services secured by Keycloak based on user groups or roles. The closest I got for the solution was to implement a custom authenticator script which performs a user attribute check, however that solution comes with a big disadvantage: you lose the SSO function.

I used the below Authenticator script:

AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");

 function authenticate(context) {
    var allowed_groups = ['foo', 'bar'];
    var username = user ? user.username : "anonymous";
    var groups = user.getGroups();
    var group_array = groups.toArray();
    
    for (var i in group_array) {
        var gn = group_array[i].getName();
        if (allowed_groups.indexOf(gn) >= 0) {
            LOG.info("Access granted for user '" + username + "' for being member of LDAP group '" + gn + "'");
            return context.success();
        }    
    }

    LOG.info("Access denied for user '" + username + ". for not being member of any of the following LDAP groups: " + allowed_groups);
    context.failure(AuthenticationFlowError.IDENTITY_PROVIDER_DISABLED, context.form().setError(
        "User doesn't have the required LDAP group membership to view this page", null).createForm("error.ftl"));
    return;
 }

Screenshot from the authentication flow:

1

Unfortunately the user object within the custom authenticator script only gets populated when the "Username Password Form" auth section is passed, otherwise the user object is null therefore you can't check user attributes such as group or role. This means the user must type their credentials every time for every clients (web services) he intends to access via Keycloak. Unfortunately not having the SSO function is not an option for us as we are running more than 10 web services.

Is there any way to achieve user attribute based client access limitation by Keycloak while still having SSO? I wonder can the user information be somehow retrieved after the "Cookie" auth section passed? I am using Keycloak version 11.0.2

Preempt answered 16/12, 2020 at 21:3 Comment(0)
S
3

Your approach is wrong. Identity provider (e.g. Keycloak with OIDC protocol) provides authentication - not authorization. Authorization should be done in your application code. Application code must decide if already authenticated user is authorized to perform action or not based on the user details provided in the access token (e.g. group membership in your case, so user groups must be available in the access token generated by the Keycloak - you already have native support for that in the Keycloak with role-ldap-mapper/group-ldap-mapper). I wouldn't hack authentication process with your approach.

Segment answered 16/12, 2020 at 21:30 Comment(0)
L
3

Your script looks good so far. To regain the SSO functionality you only need to adjust your flow slightly. It is important that you split the flow into two subflows (authentication and authorization). Below I have linked an example from my system. This way the role check works even if the login is done via cookie or Kerberos.

Login Flow for full SSO

I have additionally built my script based on Composite Roles. This way I can easily link the clients with the required roles. This eliminates the need to create a separate flow for each client, if you have multiple clients that should be restricted to different roles.

AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError");

function authenticate(context) {

    var client = session.getContext().getClient();
    var clientRole = client.getRole("feature:authenticate");

    if (!user.hasRole(clientRole)) {

        LOG.info("Access to client " + client.getName() + " denied for user " + user.username + " for not being member of the required access role");
        context.failure(
            AuthenticationFlowError.IDENTITY_PROVIDER_DISABLED,
            context.form().setError("You are not authorized to use " + client.getName() + ". Please contact the helpdesk if you need access.", null)
            .createForm("error.ftl"));
        return;
    }

    context.success();
}

Then you just have to create the "feature:authenticate" (Name specified in script) role in the client you want to restrict...

Role in the client configuration

...and link the realm role (in my case imported from an LDAP server) to the client role created above:

Link client role to realm role

Leonorleonora answered 19/12, 2020 at 16:6 Comment(1)
That is perfect! Exactly what I needed. Tried out and works like charm :) Thanks!Revolve
T
-1

Many answers, but I think they miss the point. Keycloak is only an ID provider. It says who is the users and to what roles does the user belong. It is task of the application to decide which roles can do what. For example users of which roles can log in into admin area.

Trevatrevah answered 17/8, 2022 at 14:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.