Assign User to group from registration form

I’m trying to create a custom registration form in Keycloak that will create the user and assign them to a group depending on a team drop down selection. Here is an example of my problem:


I have multiple teams that will be using keycloak and each team will have their own group in keycloak mapping out all the roles that they need access to. One app that will be using keycloak authentication is Grafana. When a user goes to Grafana and tries to register for a new account via keycloak, it creates the user, but because the user isn’t assigned to a group, they land at an error page in Grafana because they don’t have any privilege.


I’m trying to limit the amount of manual work to assign hundreds of users to groups after they register. I don’t have access to LDAP or Active Directroy.



Here is an example of what i’ve tried in my custom register.ftl template.

        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('team',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="groups" class="${properties.kcLabelClass!}">Team</label>
            </div>

            <div class="${properties.kcInputWrapperClass!}">
                <select class="${properties.kcInputClass!}" id="groups" name="groups" value="${(register.formData['groups']!'')}">
                    <option value="group1">Group 1</option>
                    <option value="group2">Group 2</option>
                    <option value="group3">Group 3</option>
                </select>

            </div>
        </div>

I was successfully able to add the group as a user attribute, and then thought I could execute a script after that uses the rest API to then assign the group, but I haven’t had much luck with that either. I would like to stay away from assigning everyone to a default “view only” group because some groups don’t need to be able to view all the apps that we will use.



If anyone has any ideas or could help point me in the right direction, please let me know. I have limited knowledge HTML & Javascript but know my way around Python.

Hi,

I could give you some pointers on how to write a custom SPI to accomplish this but these are using Java which is probably not what you want.

Maybe tell us what is not working exactly in the flow you’re having now?
My guess is you call some Javascript function now that does a Python backend call to add a group to that user when you press the register button or something?

@zonaut Thanks for the reply. I was hoping to be able to add the user group right on the registration form. The same way it maps out a users password when you fill out the form. I was afraid that that wasn’t possible so I started trying to write a python script to make some calls to the Keycloak rest api to add a user to a group, which I had no luck. My plan was to do exactly what you described. When you press the button, it submits the form and then calls my script to add the newly created user to the group they selected on the form. I started playing around with the API and a python packaged called “python-keycloak” but was hoping there would be an easier solution.

I appreciate you offering to give me some pointers to write a custom SPI, but I’m afraid it would go right over my head! lol

What’s the error you’re getting when trying to add the group to the user with your Python script?

Currently, I’m getting {‘error’: ‘HTTP 404 Not Found’} error. I’ve been playing around with the URL from the API documentation.

PUT /{realm}/users/{id}/groups/{groupId}

The last URL I tried was:
https://keycloak_url.com/auth/realms/realm_name/users/user_id/groups/group_id

I’ve also tried to set the user_id to both the ID and the name.

I’ve also tried the python-keycloak library using

keycloak_admin.update_user(
user_id=“user_name”,
payload={‘groups’: [’/group_name’]}
)

That one doesn’t give me any errors, just doesn’t add the user to the group.

Edit – Scratch all of that. I was able to successfully add a user to a group using python. I was missing the /admin/ in the URL. The next part that I’m not sure the best way to tackle, like I said, my HTML and Javascript is amateur at best, is how can I use the form to pass the newly created user_id to my script and run it?

With user_id you probably mean the UUID after the user has been created?

You won’t have access to that until after it’s creation so what you could do is search for the user in the users resource with the search query param as you will know the email or username. You can get the id from that and go from there.

You are correct. The UUID is what is needed to add a user to a group using the API. I could query the users using the API to get the UUID, but how could I call the script when the submit button is pressed?

This is my current template : register.ftl

<#import "template.ftl" as layout>
<@layout.registrationLayout; section>
<#if section = "header">
    ${msg("registerTitle")}
<#elseif section = "form">
    <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
            </div>
            <div class="${properties.kcInputWrapperClass!}">
                <input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')}" />
            </div>
        </div>

        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
            </div>
            <div class="${properties.kcInputWrapperClass!}">
                <input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')}" />
            </div>
        </div>

        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
            </div>
            <div class="${properties.kcInputWrapperClass!}">
                <input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')}" autocomplete="email" />
            </div>
        </div>



        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('team',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="user.attributes.team" class="${properties.kcLabelClass!}">Team</label>
            </div>

            <div class="${properties.kcInputWrapperClass!}">
                <select class="${properties.kcInputClass!}" id="user.attributes.team" name="user.attributes.team" value="${(register.formData['user.attributes.team']!'')}">
                    <option value=""></option>
                    <option value="group1_uuid">Group 1</option>
                    <option value="group2_uuid">Group 2</option>
                    <option value="group3_uuid">Group 3</option>
                    <option value="group4_uuid">Group 4</option>
                </select>

            </div>
        </div>


      <#if !realm.registrationEmailAsUsername>
        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
            </div>
            <div class="${properties.kcInputWrapperClass!}">
                <input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')}" autocomplete="username" />
            </div>
        </div>
      </#if>

        <#if passwordRequired>
        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
            </div>
            <div class="${properties.kcInputWrapperClass!}">
                <input type="password" id="password" class="${properties.kcInputClass!}" name="password" autocomplete="new-password"/>
            </div>
        </div>

        <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
            <div class="${properties.kcLabelWrapperClass!}">
                <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
            </div>
            <div class="${properties.kcInputWrapperClass!}">
                <input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
            </div>
        </div>
        </#if>

        <#if recaptchaRequired??>
        <div class="form-group">
            <div class="${properties.kcInputWrapperClass!}">
                <div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
            </div>
        </div>
        </#if>

        <div class="${properties.kcFormGroupClass!}">
            <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
                <div class="${properties.kcFormOptionsWrapperClass!}">
                    <span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
                </div>
            </div>

            <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
                <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
            </div>
        </div>
    </form>
</#if>
</@layout.registrationLayout>

I need it to pass the value of username and the value of the selected group id to my python script and then execute it when the submit button is pressed.

I @zonaut. I have a similar requirement. It would be great if you can provide some tips to write a custom SPI for this. Thanks in advance.

You could take a look at the spi-registration-profile example on https://github.com/zonaut/keycloak-extensions
The spi-registration-profile example disables the need for first and lastname but gives you an example on how you could add extra fields to your own form and use them.

1 Like

Hey @wymangr could you post your full (python) code that worked to add the user to the group. I am a bit confused by your process above.
Thanks

The SPI examples have been very helpful. However, I am trying to do this the Java route and am having a hard time getting the “list of all groups” in Java. Presumably, once I have the appropriate GroupModel object, I could call user.joinGroup(mygroup).

Is it possible to list all groups from Java? Trolling the source thus far has come up empty.

My current aim:

  • Alter first login flow with a custom execution
  • I have successfully referenced / edited attributes
  • The user.getGroups() method only gives me the user’s groups (which is not what I need)
  • I cannot find a user.getAllGroups() or global.getGroups() or some such class/method

The relevant method thus far:

    public void authenticate(AuthenticationFlowContext context) {
        LOG.info("Starting authenticate workflow");
        UserModel user = context.getUser();
        if (user == null) {
            context.attempted();
        } else {
            user.setSingleAttribute("uid", "1000");
            user.setSingleAttribute("gid", "1000");
            LOG.info("Getting groups");
            Set<GroupModel> all_groups = user.getGroups();

            // returning 0 groups right now - just the user's groups
            LOG.info("Got " + all_groups.size() + " groups");

            Iterator<GroupModel> it = all_groups.iterator();

            while (it.hasNext()) {
                LOG.info("Reading one group");
                LOG.info(it.next().getName());
                LOG.info(it.next().getId());
            }

            context.success();
        }
    }

EDIT: Aha! I think I found it (in ./server-spi/src/main/java/org/keycloak/models/RealmModel.java)

context.getRealm().getTopLevelGroups();

@jason, I was never able to get the python script to execute from the form, so I ended up using Javascript to take the Team name from the drop down to add hidden forms with values to map out to the users attribute in Keycloak.

function getValue() {
            var choice = document.getElementById('user.attributes.team').value;
            console.log(choice);

            if (choice == "Team 1") {

                var y = document.createElement("INPUT");
                y.setAttribute("id", "user.attribute.jenkins");
                y.setAttribute("type", "hidden");
                y.setAttribute("value", ("Team1_jenkins"));
                y.setAttribute("name", "user.attributes.jenkins");
                y.setAttribute("class", "${properties.kcInputClass!}");

                var z = document.createElement("INPUT");
                z.setAttribute("id", "user.attribute.gitea");
                z.setAttribute("type", "hidden");
                z.setAttribute("value", "Team1_gitea");
                z.setAttribute("name", "user.attributes.gitea");
                z.setAttribute("class", "${properties.kcInputClass!}");

                var parent = document.getElementById("kc-register-form");
                parent.appendChild(y);
                parent.appendChild(z);


            } else if (choice == "Team 2"){
                var y = document.createElement("INPUT");
                y.setAttribute("id", "user.attribute.jenkins");
                y.setAttribute("type", "hidden");
                y.setAttribute("value", "Team2_jenkins");
                y.setAttribute("name", "user.attributes.jenkins");
                y.setAttribute("class", "${properties.kcInputClass!}");

                var z = document.createElement("INPUT");
                z.setAttribute("id", "user.attribute.gitea");
                z.setAttribute("type", "hidden");
                z.setAttribute("value", "Team2_gitea");
                z.setAttribute("name", "user.attributes.gitea");
                z.setAttribute("class", "${properties.kcInputClass!}");

                var parent = document.getElementById("kc-register-form");
                parent.appendChild(y);
                parent.appendChild(z);
            }
        }

In this example, it will map out a “User Attribute” in Keycloak for “jenkins” and “gitea” with the value dependent on what drop down they select. From there, I just configured a Mapper under the Client to point to either the “jenkins” or “gitea” user attribute. I’m sure there is a better way to do it… but this worked so…?

As for the Python Script, I’m not 100% sure if I still have it, but I’ll take a look and paste it here if I find it.