How to authenticate in REST with Jmix User credentials?

Jmix 1.5 had /oauth/token endpoint to get an authentication token with user credentials.
Jmix 2.0 and 2.1 with Spring Security 6 has not Resource Owner Password Credentials Grant. It was deprecated.
What replacement can I use ?

There is no direct replacement of the Resource Owner Password Credentials Grant. Out of the box, Authorization Code Grant and Client Credentials Grant are supported.

Please advice how we can use Jmix 2.1 as backend for Vue e-comerce frontend where users can register themself and then login by username and password stored in Jmix database.
Redirect uses to custom Spring Security login form is not the case.

I’ve created an issue for implementing Resource owner password credentials grant in Authorization Server add-on. But I’m not sure yet that we will do that.

If none of authorization flows provided out-of-the-box works for you, then it looks like you will have to implement your own Authorization grant type (see spring docs).

In my project I`ve tried to implement password grand type for rest auth by using: Resource owner password credentials grant by gorbunkov · Pull Request #3008 · jmix-framework/jmix · GitHub

with only one difference - configuring OAuth2 token endpoint in security filter chain

@Configuration
public class CustomAuthConfig  {
    @Autowired
    private AuthServerProperties authServerProperties;
    @Bean({"authsr_CustomSecurityFilterChain"})
    @Order(JmixSecurityFilterChainOrder.AUTHSERVER_AUTHORIZATION_SERVER - 5)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(authServerProperties.getLoginPageUrl()))).cors(Customizer.withDefaults());
        http.with(new CustomTokenEndpointConfigurer(), Customizer.withDefaults());
        SecurityConfigurers.applySecurityConfigurersWithQualifier(http, "authorization-server");
        return http.build();
    }
}

everything else is the same

But in athentication provider (OAuth2ResourceOwnerPasswordCredentialsAuthenticationProvider)

   /**
     * Attempts to authenticate the passed {@link Authentication} object which contains username and password, returning
     * a fully populated <code>Authentication</code> object (including granted authorities) if successful. The
     * {@link AuthenticationManager} defined in Jmix application is used for this.
     */
    private Authentication authenticateUserInJmix(
            OAuth2ResourceOwnerPasswordCredentialsAuthenticationToken authenticationToken) {
        String username = authenticationToken.getUsername();
        String password = authenticationToken.getPassword();
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }

I`ve got an exception, when trying to execute username-password auth

Безымянный

And I have no idea how to fix it, exception thrown , when unconstrainted datamanager loading user by username.

Can you share the full stack trace of the exception you are getting? There might be something there that explains the cause of the error.
You can also create a project based on Jmix 2.3.999-SNAPSHOT, try to get the token there, debug it and compare it with your implementation.

stack trace

org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:118)
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:133)
org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
ru.dgtserv.mes.app.CustomAuthenticationProvider.authenticateUserInJmix(CustomAuthenticationProvider.java:203)
ru.dgtserv.mes.app.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:70)
org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter.doFilterInternal(OAuth2TokenEndpointFilter.java:167)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter.doFilterInternal(OAuth2ClientAuthenticationFilter.java:125)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter.doFilterInternal(NimbusJwkSetEndpointFilter.java:85)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter.doFilterInternal(OAuth2DeviceVerificationEndpointFilter.java:139)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter.doFilterInternal(OAuth2AuthorizationEndpointFilter.java:157)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter.doFilterInternal(OAuth2AuthorizationServerMetadataEndpointFilter.java:84)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.AuthorizationServerContextFilter.doFilterInternal(AuthorizationServerContextFilter.java:61)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:225)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.base/java.lang.Thread.run(Thread.java:1583)

On your screenshot I see another message:

“Authentication principal must be UserDetails”.

This RuntimeException is thrown from the io.jmix.core.security.impl.CurrentAuthenticationImpl#getUser. You can put a breakpoint there and try to figure out what principal is inside the Authentication object and why it is there.

1 Like

ok, i created test project with Jmix 2.3.999-SNAPSHOT and during auth request with password grant, dont`t event get there (io.jmix.core.security.impl.CurrentAuthenticationImpl#getUser).
so i think problem is that my additional implementation of security filterchain is wrong

@Configuration
public class CustomAuthConfig  {
    @Autowired
    private AuthServerProperties authServerProperties;
    @Bean({"authsr_CustomSecurityFilterChain"})
    @Order(JmixSecurityFilterChainOrder.AUTHSERVER_AUTHORIZATION_SERVER - 5)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(authServerProperties.getLoginPageUrl()))).cors(Customizer.withDefaults());
        http.with(new CustomTokenEndpointConfigurer(), Customizer.withDefaults());
        SecurityConfigurers.applySecurityConfigurersWithQualifier(http, "authorization-server");
        return http.build();
    }
}

i also tried to create test project on Jmix 2.2.0
i`ve replaced @Bean({“authsr_AuthorizationServerSecurityFilterChain”}) from AuthServerAutoConfiguration with aspect

@Component
public class AuthConfigAspect {
    @Autowired
    private AuthServerProperties authServerProperties;

    @Around("execution(* io.jmix.autoconfigure.authserver.AuthServerAutoConfiguration.AuthorizationServerSecurityConfiguration..authorizationServerSecurityFilterChain(..))")
    public SecurityFilterChain customizeAuthorizationServerSecurityFilterChain(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpSecurity http = (HttpSecurity) joinPoint.getArgs()[0];

        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.exceptionHandling((exceptions) -> exceptions
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(authServerProperties.getLoginPageUrl())))
                .cors(Customizer.withDefaults());
        http.with(new OAuth2ResourceOwnerPasswordTokenEndpointConfigurer(), Customizer.withDefaults());
        SecurityConfigurers.applySecurityConfigurersWithQualifier(http, "authorization-server");
        return http.build();
    }
}

everything else i just copied from Jmix 2.3.999-SNAPSHOT and it works fine,
but in my project doesnt)
i forgot to mention that my project is composite with multitenancy and we are all has jmix-security-data-starter dependency and only one with jmix-authserver-starter

ps. adding multitenancy dependency in test projects also breaking aouth2 password_grant

Yes, I see the problem with multitenancy add-on. I’ve created an issue for this.

1 Like

Thanks a lot.
And one more question. For now multitenancy does`nt supported by rest addon. Would it be supported in next releases?

We have an issue for that. It is scheduled for summer release.

Hello,

in Sample Project rest-mt-password-grant.zip I’m trying to get an access-token using a refresh-token.

1

I set authorization-grant-types=password,refresh_token.

But I get redirect to login page:

2

Hi,
You’re sending GET request, but it must be POST

1 Like

Thank you, Maxim!

Hi,
why is the Nexus repository unavailable?
“Could not resolve io.jmix.bom:jmix-bom:2.3.999-SNAPSHOT”

try create project with available stable jmix studio version, and then change bom version in build.gradle

jmix {
    bomVersion = '2.3.999-SNAPSHOT'
    projectId = 'test'
}