Implementing Resource Owner Password Grant Oauth2 authentication using REST api on Flutter

345 Views Asked by At

I am currently building an android/ios/web app in flutter that uses oauth2 authentication with backend spring boot oauth2 server.Since I am new on flutter I don't have any idea about implementing the above on flutter.I want to implement a login screen which uses 'password' grant type to request access and need to implement access token refresh mechanism using refresh token before the token gets expired.I need to implement a multiuser login with token management like in gmail app where authenticated accounts/users can switched at any time with convenience. The following link provide way to authenticate using client credential and authorization grant using oauth2_client library. client credential and authorization grant

Please help me to implement this.

Below is my backend AuthorizationServerConfigurerAdapter configuration.

import com.exp.myserver.model.Account;
import com.exp.myserver.model.CustomUserDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

@Configuration
@EnableAuthorizationServer
public class MyOAuthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    private static final String RESOURCE_ID = "restservice";

    static Logger logger = LoggerFactory.getLogger(OAuth2AuthorizationConfig.class);

    private static final int VALID_FOREVER = -1;

    // TODO externalize token related data to configuration, store clients in DB
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        logger.info(" configure(ClientDetailsServiceConfigurer clients) ");
        clients
                .inMemory()
                .withClient("clientId")
                .authorizedGrantTypes("password","refresh_token")
                .authorities("ROLE_ADMIN","ROLE_CLIENT","ROLE_SUPPORT","ROLE_MANAGER","ROLE_ACCOUNT")
                .scopes("read", "write")
                .resourceIds(RESOURCE_ID)
                .secret(passwordEncoder.encode("clientPassword")).accessTokenValiditySeconds(4800).refreshTokenValiditySeconds(VALID_FOREVER);//Integer.MAX_VALUE
    }

    /*
    * The endpoints can only be accessed by a not logged in user or a user with
    * the specified role
    */
    // TODO externalise configuration
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        logger.info(" configure(AuthorizationServerSecurityConfigurer oauthServer) ");
        oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_ADMIN')")
                .checkTokenAccess("hasAuthority('ROLE_ADMIN')");
        oauthServer.allowFormAuthenticationForClients();
        oauthServer.checkTokenAccess("permitAll()");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        logger.info(" configure(AuthorizationServerEndpointsConfigurer endpoints) ");
        endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter())
                .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    // TODO encrypt password
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        logger.info(" jwtAccessTokenConverter() ");
        JwtAccessTokenConverter converter = new CustomTokenEnhancer();
        converter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "keystorePassword".toCharArray()).getKeyPair("mykeystore"));
        return converter;
    }

    /*
    * Add custom user principal information to the JWT token
    */
    // TODO additional information fields should be get from configuration
    protected static class CustomTokenEnhancer extends JwtAccessTokenConverter {
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            logger.info(" OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) ");
            Account user = (Account) authentication.getPrincipal();

            Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
            
            CustomUserDetails custUser = (CustomUserDetails) authentication.getPrincipal();

            info.put("user_id", user.getId());
            info.put("email", user.getUserName());
            info.put("user_mode", user.getUserMode());
            info.put("company_id", user.getUserCompany().getCompanyId().getId());
            info.put("company_name", user.getUserCompany().getCompanyId().getName());
            DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
            // Get the authorities from the user
            Set<GrantedAuthority> authoritiesSet = new HashSet<>(authentication.getAuthorities());

            logger.info("GrantedAuthority authorities "+authentication.getAuthorities());

            // Generate String array
            String[] authorities = new String[authoritiesSet.size()];

            logger.info("authoritiesSet.size() "+authoritiesSet.size());

            int i = 0;
            for (GrantedAuthority authority : authoritiesSet)
                authorities[i++] = authority.getAuthority();

            info.put("authorities", authorities);
            customAccessToken.setAdditionalInformation(info);

            return super.enhance(customAccessToken, authentication);
        }
    }

    /*
    * Setup the refresh_token functionality to work with the custom
    * UserDetailsService
    */
    @Configuration
    protected static class GlobalAuthenticationManagerConfiguration extends GlobalAuthenticationConfigurerAdapter {

        @Autowired
        private UserDetailsService userDetailsService;

        @Autowired
        private PasswordEncoder passwordEncoder;

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
        logger.info(" Oauth init(AuthenticationManagerBuilder auth) ");
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
            logger.info(" Oauth init(AuthenticationManagerBuilder auth) "+auth.getDefaultUserDetailsService().toString());
        }
    }
}

I used a CustomTokenEnhancer to provide extra information for the authenticated user upon login. I am able to login using oauth2 library in flutter as below but I can not access the user information from the oauth client.

import 'package:oauth2/oauth2.dart' as oauth2;

Future<oauth2.Client> authenticateUser() async {
    // This URL is an endpoint that's provided by the authorization server. It's
    // usually included in the server's documentation of its OAuth2 API.
    final authorizationEndpoint = Uri.parse('${selCompanyUrl}oauth/token');

    // The user should supply their own username and password.
    final uName = username;
    final uPassword = password;

    // The authorization server may issue each client a separate client
    // identifier and secret, which allows the server to tell which client
    // is accessing it. Some servers may also have an anonymous
    // identifier/secret pair that any client may use.
    //
    // Some servers don't require the client to authenticate itself, in which case
    // these should be omitted.
    final identifier = 'clientID';
    final secret = 'ClientPassword';

    // Make a request to the authorization endpoint that will produce the fully
    // authenticated Client.
    var client = await oauth2.resourceOwnerPasswordGrant(
        authorizationEndpoint, uName!, uPassword!,
        identifier: identifier, secret: secret);
    print('Oauth Client : $client :: access token : ${client.credentials.accessToken} :: refresh token : ${client.credentials.refreshToken}');
    
    return client;
}
0

There are 0 best solutions below