Can't log in: multitenent application

I have created a new application in JMIX with multitenancy add-on. I can log in using the admin user but when I try to log in using the user created for a tenant, the log-in fails.

I have followed all the steps recommended as follows:

  1. User Entity

    @JmixEntity
    @Entity(name = “st_User”)
    @Table(name = “ST_USER”, indexes = {
    @Index(name = “IDX_ST_USER_ON_USERNAME”, columnList = “USERNAME”, unique = true)
    })
    public class User implements JmixUserDetails, HasTimeZone, AcceptsTenant {
    @TenantId
    @Column(name = “TENANT”)
    private String tenant;

     @Id
     @Column(name = "ID", nullable = false)
     @JmixGeneratedValue
     private UUID id;
     ..............
       ..........  
     @Override
     public String getTenantId() {
             return tenant;
         }
    
     public String getTenant() {
         return tenant;
     }
    
     public void setTenant(String tenant) {
         this.tenant = tenant;
     }
    
     public UUID getId() {
         return id;
     }
    
  2. Added tenant field in both user browser and editor screen

  3. User Edit controller

    @UiController(“st_User.edit”)
    @UiDescriptor(“user-edit.xml”)
    @EditedEntityContainer(“userDc”)
    @Route(value = “users/edit”, parentPrefix = “users”)
    public class UserEdit extends StandardEditor {

     @Autowired
     private EntityStates entityStates;
    
     @Autowired
     private PasswordEncoder passwordEncoder;
    
     @Autowired
     private PasswordField passwordField;
    
     @Autowired
     private TextField<String> usernameField;
    
     @Autowired
     private PasswordField confirmPasswordField;
    
     @Autowired
     private Notifications notifications;
    
     @Autowired
     private MessageBundle messageBundle;
    
     @Autowired
     private ComboBox<String> timeZoneField;
     @Autowired
     private ComboBox<String> tenantField;
    
     @Autowired
     private TenantProvider tenantProvider;
    
     @Autowired
     private MultitenancyUiSupport multitenancyUiSupport;
    
     @Subscribe
     public void onBeforeShow(BeforeShowEvent event) {
         String currentTenantId = tenantProvider.getCurrentUserTenantId();
         if (!currentTenantId.equals(TenantProvider.NO_TENANT)
                 && Strings.isNullOrEmpty(tenantField.getValue())) {
             tenantField.setEditable(false);
             tenantField.setValue(currentTenantId);
         }
     }
    
     @Subscribe("tenantField")
     public void onTenantFieldValueChange(HasValue.ValueChangeEvent<String> event) {
         usernameField.setValue(
                 multitenancyUiSupport.getUsernameByTenant(
                         usernameField.getValue(), event.getValue()));
     }
    
    
     @Subscribe
     public void onInitEntity(InitEntityEvent<User> event) {
         usernameField.setEditable(true);
         passwordField.setVisible(true);
         confirmPasswordField.setVisible(true);
         tenantField.setEditable(true);
     }
    
     @Subscribe
     public void onAfterShow(AfterShowEvent event) {
         if (entityStates.isNew(getEditedEntity())) {
             usernameField.focus();
         }
     }
    
     @Subscribe
     protected void onBeforeCommit(BeforeCommitChangesEvent event) {
         if (entityStates.isNew(getEditedEntity())) {
             if (!Objects.equals(passwordField.getValue(), confirmPasswordField.getValue())) {
                 notifications.create(Notifications.NotificationType.WARNING)
                         .withCaption(messageBundle.getMessage("passwordsDoNotMatch"))
                         .show();
                 event.preventCommit();
             }
             getEditedEntity().setPassword(passwordEncoder.encode(passwordField.getValue()));
         }
     }
    
     @Subscribe
     public void onInit(InitEvent event) {
         timeZoneField.setOptionsList(Arrays.asList(TimeZone.getAvailableIDs()));
         tenantField.setOptionsList(multitenancyUiSupport.getTenantOptions());
     }
    

    }

  4. Updated log-in screen as suggested

    @UiController(“st_LoginScreen”)
    @UiDescriptor(“login-screen.xml”)
    @Route(path = “login”, root = true)
    public class LoginScreen extends Screen {

     @Autowired
     private TextField<String> usernameField;
    
     @Autowired
     private PasswordField passwordField;
    
     @Autowired
     private CheckBox rememberMeCheckBox;
    
     @Autowired
     private ComboBox<Locale> localesField;
    
     @Autowired
     private Notifications notifications;
    
     @Autowired
     private Messages messages;
    
     @Autowired
     private MessageTools messageTools;
    
     @Autowired
     private LoginScreenSupport loginScreenSupport;
    
     @Autowired
     private UiLoginProperties loginProperties;
    
     @Autowired
     private JmixApp app;
    
     private final Logger log = LoggerFactory.getLogger(LoginScreen.class);
    
     @Autowired
     private MultitenancyUiSupport multitenancyUiSupport;
    
     @Autowired
     private UrlRouting urlRouting;
    
    
    
     @Subscribe
     private void onInit(InitEvent event) {
         usernameField.focus();
         initLocalesField();
         initDefaultCredentials();
     }
    
     private void initLocalesField() {
         localesField.setOptionsMap(messageTools.getAvailableLocalesMap());
         localesField.setValue(app.getLocale());
         localesField.addValueChangeListener(this::onLocalesFieldValueChangeEvent);
     }
    
     private void onLocalesFieldValueChangeEvent(HasValue.ValueChangeEvent<Locale> event) {
         //noinspection ConstantConditions
         app.setLocale(event.getValue());
         UiControllerUtils.getScreenContext(this).getScreens()
                 .create(this.getClass(), OpenMode.ROOT)
                 .show();
     }
    
     private void initDefaultCredentials() {
         String defaultUsername = loginProperties.getDefaultUsername();
         if (!StringUtils.isBlank(defaultUsername) && !"<disabled>".equals(defaultUsername)) {
             usernameField.setValue(defaultUsername);
         } else {
             usernameField.setValue("");
         }
    
         String defaultPassword = loginProperties.getDefaultPassword();
         if (!StringUtils.isBlank(defaultPassword) && !"<disabled>".equals(defaultPassword)) {
             passwordField.setValue(defaultPassword);
         } else {
             passwordField.setValue("");
         }
     }
    
     @Subscribe("submit")
     private void onSubmitActionPerformed(Action.ActionPerformedEvent event) {
         login();
     }
    
     private void login() {
         String username = usernameField.getValue();
         String password = passwordField.getValue();
    
         if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
             notifications.create(Notifications.NotificationType.WARNING)
                     .withCaption(messages.getMessage(getClass(), "emptyUsernameOrPassword"))
                     .show();
             return;
         }
    
         // add tenantId prefix if it was provided in the URL
         username = multitenancyUiSupport.getUsernameByUrl(username, urlRouting);
    
         try {
             loginScreenSupport.authenticate(
                     AuthDetails.of(username, password)
                             .withLocale(localesField.getValue())
                             .withRememberMe(rememberMeCheckBox.isChecked()), this);
         } catch (BadCredentialsException | DisabledException | LockedException e) {
             log.info("Login failed", e);
             notifications.create(Notifications.NotificationType.ERROR)
                     .withCaption(messages.getMessage(getClass(), "loginFailed"))
                     .withDescription(messages.getMessage(getClass(), "badCredentials"))
                     .show();
         }
     }
    

    }

  5. Only one thing I didn’t find to update is the following recommendation in the user guide:

Configuring Security

When configuring roles for tenant users, exclude tenant-id attributes from entity attribute policies, so users won’t see them. For example, if the Customer entity is tenant-specific and has tenant attribute annotated with @TenantId, the role that gives access to the entity should list the attributes explicitly and omit tenant:

@ResourceRole(name = "Users", code = "users", scope = "UI")
public interface UsersRole {
    // ...

    @EntityAttributePolicy(
            entityClass = Customer.class, attributes = {"region", "name", "version", "id"},
            action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = Customer.class, actions = EntityPolicyAction.ALL)
    void customer();

Thanks for your help.

Do you provide tenant id of users as described here?

I looked at but not sure. Do I have to use the user name during log-in like this “t2|alice” ? I was trying just “alice”

Yes, either enter the name with tenant prefix or use a URL parameter as described in the doc. Also, make sure you have made all required changes in your project, see Configuring Users.

1 Like

Thanks, will try…

Hi. I’m having exactly the same problem as @mortoza_khan
Follow all the steps, but I only can login as admin.
I tried specifying tenantId in the URL and entering manually tenantId+user in the user field as well.

Well, I found that the reason for not loggin in was that I forgot to assign a role to the new user.

Hi, what rule did you add to be able to login? because also for me I need to add full-access to user for login, indeed I can’t access with tenant user. Thanks

Hi Marco, as far as I remember, I created a ResourceRole wich I attach:
UsuariosRole.java (2.4 KB)

Then, when I create a user, I assign these role.

Once the user created, assign the role:
imagen

imagen

And that I think was all as far as I can remember.
All this was developed with Jmix 1.3.0, I don’t know how it’s with the new versions. (>1.4)

Hope this helps!

German Turriziani

Thanks, i added UI:minimal access and it seems to work

1 Like

Sorry Marco, that was the real solution. What happens is that I did that a long time ago and the truth is that I did not remember exactly.

1 Like

Hi @neosoft.gt
Login is also working for me but it is only with the tenant prefix to the user name. Did you try to configure without the tenant name prefix when logging in? if so can you share the steps and code snippets?

Hi @mortoza_khan
Exactly, the same behavior in my case.

imagen
This works! :+1:

imagen
imagen

This don’t :man_shrugging:

As soon as I can I’ll try it with JMIX 1.5.1

German Turriziani