45

I have a client in keycloak for my awx(ansible tower) webpage.

I need only the users from one specific keycloak group to be able to log in through this client.

How can I forbid all other users(except from one particular group) from using this keycloak client?

peterh
  • 11,875
  • 18
  • 85
  • 108
lukasell
  • 761
  • 1
  • 8
  • 10
  • You can get help from this documents its clearly mentioned how to achieve this https://www.keycloak.org/docs/3.3/server_admin/topics/admin-console-permissions/fine-grain.html – Subodh Joshi Jan 22 '19 at 12:07
  • 1
    You have probably posted a wrong link? Admin Console Access Control and Permissions only explains how can the permissions be set internally for each user/group. My problem is how to allow login through this client only to several users from keycloak – lukasell Jan 22 '19 at 14:00
  • If you cannot find the flow execution "script", see this link: https://stackoverflow.com/questions/53390134/keycloak-script-authenticator-missing – marconfus Aug 01 '19 at 12:54
  • A perfectly working solution was provided here: https://stackoverflow.com/questions/65331061/user-attribute-based-web-service-access-control-by-keycloak – András Gábor Soós Jan 18 '22 at 13:04
  • The procedure from https://stackoverflow.com/a/68509979/675721 worked for me. Choose "Condition - User Group" instead of "Condition - User Role" as indicated in answer – Codebling Oct 20 '22 at 22:25

10 Answers10

21

I found a solution which does not require the scripts extension or any changes on the flow.

The key for this solution are the Client Scopes. An application which wants to to authorize a user needs a scope like email or uid, right? What if you only pass them to an application if a user is in a specific group?

In the following, my client application name is App1.

Solution:

  1. Go to your client roles (realm -> Clients -> click App1 -> Roles)
  2. Click 'Add Role' -> enter Name (e.g. 'access') -> click 'Save'
  3. Go to Client Scopes (realm -> Client Scopes)
  4. Click on the scope which is needed by your client application (e.g. 'email')
  5. Assign Client Role 'access' in 'Scope' Tab by choosing client application 'App1' in Drop Down 'Client Roles'

Now, you won't be able to log into your client application App1 anymore, as the role 'access' is not assigned to any user or group. You can try.

Let's create a new group and assign the role and a user to it.

  1. Create Group (realm -> Groups -> Click 'New' -> enter Name 'App1 Users' -> Click Save)
  2. In the Group, choose 'Role Mappings', choose 'App1' in Client Roles drop down, and assign the role 'access'
  3. Assign User to 'App1 Users' (realm -> Users -> Click on User -> Groups -> Select 'App1 Users -> Click Join)

Voila, the chosen user can log into App1.

Michael Jobst
  • 245
  • 2
  • 2
  • 1
    This works well. Instead of using adding another role to the client it is also possible to add a global role (`nextcloud-access` or something like that), and using that in step 4. – Karalga Jan 15 '22 at 17:43
  • 2
    is it possible to restrict users based on application? for ex, I have an application at domain.com and another at domain.com/jenkins. Can I restrict a group of users only for the domain.com and a set users only for domain.com/jenkins? – Wonderhost Apr 25 '22 at 05:27
  • Yes, you can't log in anymore. Haha. But in my django case users get a 500: NOT NULL constraint failed: django_keycloak_keycloakuserautoid.email – EinEsellesEniE Aug 17 '22 at 13:04
  • 1
    This did not work for me - users were not prevented from authenticating (Keycloak 19.0.2) – Codebling Oct 20 '22 at 22:24
  • Like @Codebling mentioned this also didn't work for me on Keycloak 20.0. Access tokens can still be received. The only thing I noticed is that the wanted `scope` wasn't rendered to the access token so one workaround could be that your resource server (your app to be secured) checks if a certain scope is present in the token. – Robin Windey Nov 07 '22 at 10:50
  • @RobinWindey https://stackoverflow.com/a/68509979/675721 worked for me – Codebling Nov 07 '22 at 16:33
17

On Keycloak admin console, go to Clients menu, select your client. On the client configuration page, set Authorization Enabled: On, click Save. A new Authorization tab should appear, go to it, then to the Policies tab underneath, click Create Policy and select Group-based policy. There, you can restrict access to specific groups, assuming you have defined your groups via the Groups menu already.

--EDIT 2019-11-08--

As mentioned in comments, Client Protocol must be set to openid-connect and Access Type must be set to confidential, in order to make the Authorization Enabled option visible.

cdan
  • 3,470
  • 13
  • 27
  • This would work perfectly with openid. But in SAML there is no choice to enable authorization by a button. – lukasell Jan 27 '19 at 02:11
  • Indeed :-( Now I'm aware of this (big) Keycloak limitation for SAML. Glad you found a workaround. – cdan Jan 27 '19 at 15:15
  • 1
    For others who don't have the 'Authorization Enabled', set Access Type to "confidential", then it will appear. – Kevin C Nov 01 '19 at 13:12
  • 20
    I followed the instructions, but the authentication is not blocked in any way. It is not working for me. – Réda Housni Alaoui Dec 20 '19 at 16:54
  • 11
    This will not work, since Authorization in Keycloak refers to using the [Authorization Services API](https://www.keycloak.org/docs/latest/authorization_services/). That is something that the Client application would have to explicitly support and enforce, Keycloak itself does **not** enforce the policies. – F30 Jul 30 '20 at 17:34
  • This did not work for me, as the client is confidential but Keycloak JS client has dropped support for sending client credentials, and you will get a CORS error in the browser if you don't send the credentials when logging in. – Asu Nov 01 '21 at 22:51
8

If it can help, here is a script which helps implementing this behaviour for any client: if the client contains a given role (here it is called feature:authenticate), then the script checks whether the user has the role and shows an error page (a new template that needs to be deployed in the theme) if not.

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

 function authenticate(context) {
    var MANDATORY_ROLE = 'feature:authenticate';
    var username = user ? user.username : "anonymous";

    var client = session.getContext().getClient();

    LOG.debug("Checking access to authentication for client '" + client.getName() + "' through mandatory role '" + MANDATORY_ROLE + "' for user '" + username + "'");

    var mandatoryRole = client.getRole(MANDATORY_ROLE);

    if (mandatoryRole === null) {
        LOG.debug("No mandatory role '" + MANDATORY_ROLE + "' for client '" + client.getName() + "'");
        return context.success();
    }

    if (user.hasRole(mandatoryRole)) {
        LOG.info("Successful authentication for user '" + username + "' with mandatory role '" + MANDATORY_ROLE + "' for client '" + client.getName() + "'");
        return context.success();
    }

    LOG.info("Denied authentication for user '" + username + "' without mandatory role '" + MANDATORY_ROLE + "' for client '" + client.getName() + "'");
    return denyAccess(context, mandatoryRole);
 }

 function denyAccess(context, mandatoryRole) {
    var formBuilder = context.form();
    var client = session.getContext().getClient();
    var description = !mandatoryRole.getAttribute('deniedMessage').isEmpty() ? mandatoryRole.getAttribute('deniedMessage') : [''];
    var form = formBuilder
        .setAttribute('clientUrl', client.getRootUrl())
        .setAttribute('clientName', client.getName())
        .setAttribute('description', description[0])
        .createForm('denied-auth.ftl');
    return context.failure(AuthenticationFlowError.INVALID_USER, form);
 }
Allan
  • 177
  • 1
  • 5
  • How do we implement and use this? This is pretty unclear, even if it's supposed to be straight forward. – Kevin C Nov 01 '19 at 10:44
  • Got it working (submitted details on deployment); the only script modification I had to make: `return context.failure(AuthenticationFlowError.CLIENT_DISABLED);` instead of using the custom function `denyAccess` (caused trouble with the default theme). – Skyr Apr 01 '20 at 21:08
8

Follow-up to Allan's answer: His approach is working (for me ;-) ), though I had some struggle on how to deploy it. This is how I did it:

  • Bundle script in a JAR file as documented here, deploy it by copying to standalone/deployments/ (see manual link)
  • Enable scripts: Start Keycloak with -Dkeycloak.profile.feature.scripts=enabled
  • In your realm, create a new flow. Duplicate the Browser flow in a required subflow, and add the script authenticator as final (required) element: Screenshot of flow config
  • Now add to all clients which should be restricted a client role feature:authenticate. Users which don't bear that role won't get access to the application.
Skyr
  • 980
  • 7
  • 12
  • Hi, I'm using this method but in the script user is null, do you know why it does not pass the user even though it is logged in? – Mohammad Apr 06 '20 at 14:43
  • My mistake was that I didn't duplicate the whole Browser Flow, but I had the sub-flows over required role execution only. thanks anyway! – Mohammad Apr 09 '20 at 08:41
  • I used to use javscript authenticators to deploy the script but there are [deprecated now](https://www.keycloak.org/docs/latest/server_admin/#script-authenticator). Your approach is the [correct one](https://www.keycloak.org/docs/latest/server_development/#_script_providers) if you use newer versions of KC. – Allan Jun 16 '20 at 13:05
  • In step 3: Is it like this? We should create an empty flow (not copy an existing one) and then create an empty execution named "Browser Flow" and one by one create subflows similar to the one in "Browser"? That is, there is no "Duplicate" Functionality in Keycloak that does this in batch? Thanks. – Alireza Jan 22 '21 at 09:36
  • @Alireza at least at the time I did this, no (or I didn't find it ;-) ) – Skyr Jan 23 '21 at 13:03
6

I solved it like this:

  1. Create a new role in Keycloak.
  2. Assign this role to the group.
  3. Create a new authentication script in Kycloak. Configure which role is allowed upon login (e.g. user.hasRole(realm.getRole("yourRoleName"))).
  4. In the client's settings, under "Authentication Flow Overrides", choose the authentication script that was just created.
kba
  • 19,333
  • 5
  • 62
  • 89
lukasell
  • 761
  • 1
  • 8
  • 10
4

You can use this extension to restrict access to a specific group: https://github.com/thomasdarimont/keycloak-extension-playground/tree/master/auth-require-group-extension

dmorlock
  • 1,993
  • 4
  • 18
  • 22
  • this and other scripts in the answers does not work on Keycloak 9, I get null user even though user is logged in – Mohammad Apr 07 '20 at 10:13
2

according docu https://www.keycloak.org/docs/6.0/server_admin/#executions u have to active that feature to add some custom scripts with "add execution".

bin/standalone.sh|bat -Dkeycloak.profile.feature.scripts=enabled

@Allan solution with feature:authenticate looks good to me

2

I tried Allan's solution and it is working fine using Keycloak 11.0.3 but it has some cons mentioned below. Here is my solution for the authenticator script which does not grant access for users if they are not member at least one of the given groups. In such case a unique error message is shown.

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

There are two minor user experience related cons with this solution worth mentioning:

  • When a not logged in user tries to connect to a client which access gets denied by the authenticator script the whole authentication flow is considered failure. This means the user doesn't get logged in into Keycloak despite the fact they provided the correct credentials
  • When a logged in user tries to connect to a client which access gets denied by the authenticator script the Keycloak login page is displayed (without showing any error message) which is deceptive as the user can have the false feeling they are not logged in

In addition if you maintain multiple clients and you need to have different groups (or roles) checked per client then you have to implement as many new authentication flows as many different checks you need. In short the solution works, but it has some disadvantages. I believe a simple feature such as restricting the access based on groups or roles is essential for an identity and access management system and should be supported natively!

  • There is an another disadvantage of this solution. You have to set the Form auth type to required otherwise the user variable provides null in the script. By doing this users are forced to authenticate themselves every time (for every clients) hence it is no longer SSO solution – András Gábor Soós Dec 04 '20 at 14:19
  • There is a working solution provided which eliminates the mentioned cons thus SSO functionality remains: https://stackoverflow.com/questions/65331061/user-attribute-based-web-service-access-control-by-keycloak – András Gábor Soós Jan 18 '22 at 13:07
2

2021 year - Keycloak 7.4.1.GA

I solved it like this for SAML2:

  1. Add new Authentication flow (Just copy existing one)

  2. Add execution "Group Access Observer" and set it as Required

  3. Actions -> Config on Group Access Observer line

  4. Fill group name

  5. Go to your client and change Authentication flow to created now.

Best Regards

example auth flow

example of config

h0x91B
  • 1,211
  • 15
  • 19
1

With Keycloak >= 13.x you may want to try the "Allow/Deny Access" authenticators with conditions. You can assign a role to a group and build the condition based on the role.

If that is not flexible enough, try out this library that I have build to solve exactly that issue.

sventorben
  • 1,597
  • 4
  • 17