Doing custom stuff when a user logs in

If you want do some custom checks when a user logs in, it is possible to override the Mendix login mechanism.

With this, you can do a lot of useful things. For example:

  • Implement a custom user blocking mechanism, e.g. block all users that are linked to a certain company.
  • Only allow users to log in from certain IP addresses
  • Dynamically assign user roles when logging in
  • Etc. etc. (leave your comment below for more ideas!)

To override the standard login, we need to do some Java coding. Create a new Java action ReplaceLoginAction, with no parameters. Call this Java action on startup of the application, by adding a startup microflow, and adding a call to this Java action there. Add the following Java code to the Java action:

		// BEGIN USER CODE
		LoginActionListener loginActionListener = new LoginActionListener();
		loginActionListener.addReplaceEvent(CustomLoginAction.class.getName());
		Core.addUserAction(CustomLoginAction.class);
		Core.addListener(loginActionListener);
		
		return true;
		// END USER CODE

You need to added 2 Java class files to your module’s Javasource folder:

LoginActionListener.java:

import com.mendix.core.Core;
import com.mendix.core.action.user.LoginAction;
import com.mendix.systemwideinterfaces.core.UserActionListener;

public class LoginActionListener extends UserActionListener<LoginAction>
{
	public LoginActionListener()
	{
		super(LoginAction.class);
	}

	@Override
	public boolean check(LoginAction action)
	{
		Core.addUserAction(CustomLoginAction.class);
		return true;
	}
}

And CustomLoginAction.java:

public class CustomLoginAction extends UserAction<ISession>
{
	private String userName;
	private String password;
	private IMxRuntimeRequest request;
	private String currentSessionId;
	public final static String USER_NAME_PARAM = "userName";
	public final static String PASSWORD_PARAM = "password";


	public CustomLoginAction(Map<String, ? extends Object> params) {
		super(Core.createSystemContext());
		this.userName = (String) params.get(USER_NAME_PARAM);
		this.password = (String) params.get(PASSWORD_PARAM);
		this.currentSessionId = (String)params.get("currentSessionId");
	    this.request = (IMxRuntimeRequest)params.get("request");
	}
	
	
	@Override
	public ISession executeAction() throws Exception
	{
		IUser user = Core.getUser(getContext(), this.userName);
		if (user == null)
			throw new AuthenticationRuntimeException("Login FAILED: unknown user '" + this.userName + "'.");
		else if (user.isWebserviceUser())
			throw new AuthenticationRuntimeException("Login FAILED: client login attempt for web service user '" + this.userName + "'.");
		else if (user.isAnonymous())
			throw new AuthenticationRuntimeException("Login FAILED: client login attempt for guest user '" + this.userName + "'.");
		else if (user.isActive() == false)
			throw new AuthenticationRuntimeException("Login FAILED: user '" + this.userName + "' is not active.");
		else if (user.isBlocked() == true)
			throw new AuthenticationRuntimeException("Login FAILED: user '" + this.userName + "' is blocked.");
		else if (user.getUserRoleNames().isEmpty())
			throw new AuthenticationRuntimeException("Login FAILED: user '" + this.userName + "' does not have any user roles.");
				
                // You can even call Microflows here!, like this:
		Map<String, Object> parameters = new HashMap<String, Object>();
		parameters.put("User", user.getMendixObject());
                //this will call the microflow LoginAllowed that should have a User parameter and returns true if the user is allowed to log in
		boolean allowed = Core.execute(getContext(), "MyModule.LoginAllowed", parameters); 
		if (!allowed)
			throw new AuthenticationRuntimeException("Login FAILED: user "+this.userName);

		if (!Core.authenticate(Core.createSystemContext(), user, this.password)) //password check
			throw new AuthenticationRuntimeException("Login FAILED: invalid password for user '" + user.getName() + "'.");

		ISession session = Core.initializeSession(user, this.currentSessionId);

		return session;
	}
	
}

This executeAction() method will be called when a login-attempt is made, and will do the standard checks that Mendix does when logging in, plus call a microflow LoginAllowed in which you can do your own custom stuff. This can, for example, check whether the application is open for non-admin users, like this:

Microflow LoginAllowed
Microflow LoginAllowed

This microflow will be called by the CustomLoginAction in system context, which allows you to do almost anything! Just be careful for security and performance issues if you do too much here 🙂

12 responses to “Doing custom stuff when a user logs in”

  1. […] Note: You could also implement a more sophisticated blocking mechanism yourself, by added your own attribute(s) to Administration.Account, an then do a check on these attribute when the user logs in, as explained in my previous post. […]

  2. Michael Avatar
    Michael

    Hi Bart,

    Trying to implement this function but i receive an error on the first line.
    LoginActionListener loginActionListener = new LoginActionListener();

    Any ideas?

    1. Bart Groot Avatar
      Bart Groot

      I forgot to include the source for LoginActionListener. This is now updated.

  3. Olivier Avatar
    Olivier

    Hi Bart,

    Thanks for the write up and explanation, great help!
    However, with Mendix 6 this no longer works as it uses Deprecated libraries. Could you update this post?

    thanks!

  4. Mohan Avatar
    Mohan

    Is it still working with Mendix7? As mentioned above I tried to add this to startup microflow some how startup microflow is not triggered on app startup. So I called the microflow from default home page but the listener code is not called on signin button click.

  5. Alexandre Avatar
    Alexandre

    The way to register a listener has changed. You new to use this :

    Core.getListenersRegistry().addListener(loginActionListener);

  6. Sharon Avatar
    Sharon

    Hi Bart, I’ve tried to implement this in Mendix 8. I’m not a java expert so it took a little work.
    I can see this as being extremely powerful, however i can’t seem to get the correct error message back to the user after logon (if allowed is returned false) after an exception is forced.:
    boolean allowed = Core.execute(getContext(), “MyModule.LoginAllowed”, parameters);
    if (!allowed)
    throw new AuthenticationRuntimeException(“Login FAILED: user “+this.userName);

    I’ve debugged and it seems my custom microflow is returning the correct boolean whether or not i want the user to be logged in.
    I get a generic username and password error message, instead of the above.
    Any ideas?

    1. MD Avatar
      MD

      Bump.

      Any ideas on this? Even the default Mendix login machinery returns a generic “Invalid username or password” error when the user is temporarily blocked, rather than something more meaningful like “Maximum login attempts exceeded – try again later”.

      Being able to present a custom response message seems like a trivial thing – why does Mendix have to make it so obscure/difficult??

      1. Anonymous Avatar
        Anonymous

        Having a generic message instead of something specific is actually a security precaution to give an attacker as little info as possible.

      2. Jeroen Avatar
        Jeroen

        Because of security reasons

  7. Pavan Avatar
    Pavan

    Is this code still relevant and working for Mendix 8+? I am trying but get error

    —–

    {“type”:”LoginAction”,”user”:”superadmin”},”after”:[],”type”:”EventExtendedAction”}’, all database changes executed by this action were rolled back
    at com.mendix.basis.actionmanagement.CoreActionHandlerImpl.processErrorState(CoreActionHandlerImpl.scala:151)

    Caused by: com.mendix.core.CoreRuntimeException: An error occurred while instantiating action ‘CustomLoginAction’
    at com.mendix.basis.actionmanagement.ActionRegistry.$anonfun$instantiatorFor$1(ActionRegistry.scala:59)

    Caused by: java.lang.IllegalArgumentException: wrong number of arguments
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

  8. Żaneta Avatar
    Żaneta

    Can I navigate to another page (for example when I want to show another form with code for 2FA) inside CustomLoginAction? I was trying to use microflow with ShowPage action, but it shows Login failed message on Login Form and nothing happens.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.