[keycloak-user] Per-client authorization

Chris Boot lists at bootc.boo.tc
Tue Aug 20 12:27:01 EDT 2019


On 20/08/2019 12:29, Chris Boot wrote:
> Hi all,
> 
> I'm trying to restrict which OIDC clients users can login to based on
> roles or group membership. I can't believe this isn't something
> built-into Keycloak yet, but it seems that way.
[snip]
> As far as I can tell, the only way to make this work is using a custom
> authentication flow: https://stackoverflow.com/a/54384513/9531301

Hi all,

Because this question seems to have been asked over and over and
precious few answers have appeared, and I've spent all day hacking at
this, here is what I've come up with. I'm running Keycloak 6.0.1 for
anyone who stumbles across this post in search results in the future.

First, enable the "scripts" profile in Keycloak. Basically pass
"-Dkeycloak.profile.feature.scripts=enabled" on the command-line or
whatever variant of that which makes sense for your environment.

Then, in your relevant realm, copy the Browser and/or Direct Grant
flows, and call it something like "Browser + Required Roles" or whatever.

In your new flow, press "Add execution" and add a Script execution.
Under the Actions menu for the new Script, choose Config. I configured
mine as:

Alias: require-client-roles
Script Name: require-client-roles
Description: Check that a user has all required roles assigned for a client

Paste in the script below (also available at
http://paste.debian.net/1096714/).

Once saved, go back to your flow and change the Requirement to REQUIRED.


Now you need to configure your client(s):

- Pick the relevant client in Clients.
- In the Settings tab, expand "Authentication Flow Overrides" and choose
your new custom flow(s).
- In the Roles tab, create a new role.
- The name and description don't matter.
- Once created, go into Attributes and set a new attribute "required" to
"true". This is what the script looks for.

At this point, nobody should be able to login to the client anymore.
Users should be able to login again once the new role is assigned to them.

> Is there a way of stopping such clients from being shown on the Account
> Management => Applications screen without globally removing the
> offline_access role for all users?

I'd still like answers for this one. Basically pretending that "Offline
access" isn't there should do the trick, but how? A custom theme?

I hope this helps!
Chris

The script:

/*
 * Check that a user has all required roles assigned for a client.
 *
 * This implements per-client authentication for Keycloak. Roles added to a
 * client can have the "required" attribute set to "true", which will
mark them
 * as being required. Any users who do not have all the required roles
will be
 * unable to authenticate using this script.
 *
 * Copyright (c) 2019 Tiger Computing Ltd.
 * Author: Chris Boot <crb at tiger-computing.co.uk>
 */

// import enum for error lookup
AuthenticationFlowError =
Java.type("org.keycloak.authentication.AuthenticationFlowError");

function authenticate(context) {

    var username = user ? user.username : "anonymous";
    LOG.info(script.name + ": trace auth for: " + username);

    var client = session.getContext().getClient();
    LOG.info(script.name + ": auth to client: " + client.getClientId());

    if (user) {
        // Find all client roles marked as required
        var requiredRoles = Java.from(client.getRoles())
            .filter(function(role) {
                var required = role.getFirstAttribute("required");
                return (required && required != "false");
            });

        // Play it safe: check that we have found at least one required role
        if (requiredRoles.length === 0) {
            LOG.warn(script.name + ": no required roles found, failing
safe");
            context.failure(AuthenticationFlowError.INVALID_USER);
            return;
        }

        // Check that the user has all required roles
        if (requiredRoles.every(function(role) user.hasRole(role))) {
            LOG.info(script.name + ": user has all required roles");
            context.success();
            return;
        }

        // The user is missing one or more required roles
        var missingRoles = requiredRoles
            .filter(function(role) !user.hasRole(role))
            .map(function(role) role.getName());

        LOG.warn(script.name + ": missing role(s): " + missingRoles);
        context.failure(AuthenticationFlowError.INVALID_USER);
        return;
    }

    LOG.warn(script.name + ": denying anonymous authentication");
    context.failure(AuthenticationFlowError.INVALID_USER);
}

-- 
Chris Boot
bootc at boo.tc


More information about the keycloak-user mailing list