Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keycloak - read-only user attributes

Tags:

keycloak

I want to keep some information in Keycloak as custom user attributes.

Some of them should be managed by the user itself. Other attributes should be managed only by a Keycloak administrator. Attributes managed by the administrator should be read-only visible in the "Edit account" web page for the user.

I went through the guide to add custom user attributes in this page and customized the "Edit account" web page.

My question is: Is it ensured that the user cannot change the attribute that is meant as read-only for the user? E.g. by submitting a form where he/she sends correct data that will be automatically mapped on the server side to the user attribute.

like image 278
Apolo-11 Avatar asked Oct 21 '25 16:10

Apolo-11


1 Answers

For what you've said, it seems that you have three choices.

One would be to keep the keycloak "Edit Account" page and use an update profile listener to check what attributes are stored or which ones are updated by who, something like this:

public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
    @Override
    public InitiatedActionSupport initiatedActionSupport() {
        return InitiatedActionSupport.SUPPORTED;
    }

    @Override
    public void evaluateTriggers(RequiredActionContext context) {
    }

    @Override
    public void requiredActionChallenge(RequiredActionContext context) {
        Response challenge = context.form()
                .createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
        context.challenge(challenge);
    }

    // Check the custom attribute 1 not being modified by the user
    @Override
    public void processAction(RequiredActionContext context) {
        EventBuilder event = context.getEvent();
        event.event(EventType.UPDATE_PROFILE);
        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
        UserModel user = context.getUser();
        KeycloakSession session = context.getSession();
        RealmModel realm = context.getRealm();

        String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
        String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1") 

        if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
            Response challenge = context.form()
                    .setError("User cannot change the attribute")
                    .setFormData(formData)
                    .createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
            context.challenge(challenge);
            return;
        }
        context.success();

    }


    @Override
    public void close() {

    }

    @Override
    public RequiredActionProvider create(KeycloakSession session) {
        return this;
    }


    @Override
    public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
        if (displayType == null) return this;
        if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
        return ConsoleUpdateProfile.SINGLETON;
    }



    @Override
    public void init(Config.Scope config) {

    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {

    }

    @Override
    public String getDisplayText() {
        return "Update Profile";
    }


    @Override
    public String getId() {
        return UserModel.RequiredAction.UPDATE_PROFILE.name();
    }
}

What I don't know is if this listener will be called when you update the profile from your client application too. If it gets called, you'll need to check which is the logged in client, if it's the public client do not let update the attributes, if it's your service client, let it.

The second one would be to only let your service client update the user profiles and make a custom view in your application which sends a form POST to your client, instead of to keycloak directly. This way you can validate it in the service before sending it to keycloak.

The third one is to implement a FormAction interface, which would allow you to validate the incoming form at server side:

The core interface you have to implement is the FormAction interface. A FormAction is responsible for rendering and processing a portion of the page. Rendering is done in the buildPage() method, validation is done in the validate() method, post validation operations are done in success().

@Override
public void validate(ValidationContext context) {
    MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
    UserModel user = context.getUser();
    KeycloakSession session = context.getSession();
    RealmModel realm = context.getRealm();

    String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
    String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1") 

    if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
        Response challenge = context.form()
                .setError("User cannot change the attribute")
                .setFormData(formData)
                .createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
        context.challenge(challenge);
        return;
    }
    context.success();
}
like image 147
Xtreme Biker Avatar answered Oct 26 '25 18:10

Xtreme Biker



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!