skip to Main Content

My stack is:

  • Ionic 2
  • Java Spring
  • JWT authentication

and I want to implement a Social login button (Facebook, Google etc.) inside my app with the respective cordova plugins, that logs in the user and validates him on my existing custom server-side API and store his/her data. I wasn’t able to find any good tutorial on how to do this.

I want my user to be persisted in the database with a random password and be able to login from my app.

I was imagining something along the lines of:

(client-side)

FB.login(function(userDetailsAndToken) {
    myBackendAPI.socialLogin(userDetailsAndToken).then(function(user) {
        //Successfully logged in with Facebook!
        this.user = user;
    }
}

and in the backend (Java Spring):

@PostMapping("/social/account")
public ResponseEntity socialAccount(@Valid @RequestBody FacebookDTO facebookDTO) {
    validateSocialUser(facebookDTO);

    //if user exists return invalid etc.
    Optional<User> existingUser = userRepository.findOneByEmail(facebookDTO.getEmail());
    if (existingUser.isPresent()) {
        return ResponseEntity.badRequest();
    }

    //User doesn't exist. Proceed with login 
    //map from social to my User entity
    User user = socialMapper.toEntity(facebookDTO);

    userService
        .createUser(user.getLogin(), user.getPassword(),
            user.getFirstName(), user.getLastName(),
            user.getEmail().toLowerCase(), user.getImageUrl(),
            user.getLangKey());
    return new ResponseEntity<>(HttpStatus.CREATED);
}

Is this possible and secure? Any good resources/libraries or guidelines on how to achieve that?

3

Answers


  1. Here is a sample demo about working with FB and Google Auth , but i’m not from a Java background so you will be finding only client side solution.

    service

    let’s implement the logic for the login functionality. Create an oauth.service.ts file in the oauth folder and paste the following code in there:

    import { Injectable, Injector } from '@angular/core';
    import { FacebookOauthProvider } from './facebook/facebook-oauth.provider';
    import { IOathProvider } from './oauth.provider.interface';
    import { GoogleOauthProvider } from './google/google-oauth.provider';
    import { OAuthToken } from './models/oauth-token.model';
    @Injectable()
    export class OAuthService {
        private oauthTokenKey = 'oauthToken';
        private injector: Injector;
    constructor(injector: Injector) {
            this.injector = injector;
        }
    login(source: string): Promise {
            return this.getOAuthService(source).login().then(accessToken => {
                if (!accessToken) {
                    return Promise.reject('No access token found');
                }
    let oauthToken = {
                    accessToken: accessToken,
                    source: source
                };
                this.setOAuthToken(oauthToken);
                return oauthToken;
            });
        }
    getOAuthService(source?: string): IOathProvider {
            source = source || this.getOAuthToken().source;
            switch (source) {
                case 'facebook':
                    return this.injector.get(FacebookOauthProvider);
                case 'google':
                    return this.injector.get(GoogleOauthProvider);
                default:
                    throw new Error(`Source '${source}' is not valid`);
            }
        }
    setOAuthToken(token: OAuthToken) {
            localStorage.setItem(this.oauthTokenKey, JSON.stringify(token));
        }
    getOAuthToken(): OAuthToken {
            let token = localStorage.getItem(this.oauthTokenKey);
            return token ? JSON.parse(token) : null;
        }
    }
    

    Authentication provider and token interfaces
    As we have already mentioned, the IOathProvider should include a login() function. Therefore, we should set the following interface that will act as an abstract type/model for the IOathProvider object. Create an oauth.provider.interface.ts file in the oauth folder and include the following lines in it:

    export interface IOathProvider {
        login(): Promise;
    }
    

    Facebook and Google authentication services

    As a next step, we should implement the services for each one of the authentication providers our app has, i.e. FacebookOauthProvider and GoogleOauthProvider.

    Install dependencies

    Here is when the ng2-cordova-oauth library comes handy. We can install it by executing the command:
    npm install ng2-cordova-oauth --save

    Also, our app relies on the Cordova InAppBrowser plugin. We are going to install it with:

    ionic plugin add cordova-plugin-inappbrowser

    Do not forget to include cordova-plugin-inappbrowser in your package.json file too, so it can be installed with the rest of the plugins anytime you install your project from scratch.

    Implement the Facebook and Google authentication providers

    Let’s create the facebook-oauth.provider.ts file under the oauth/facebook/ path. In this file, include the code in the snippet:

     import { Injectable } from '@angular/core';
        import { Http } from '@angular/http';
        import { IOathProvider } from '../oauth.provider.interface';
        import { CordovaOauth } from 'ng2-cordova-oauth/oauth';
        import { Facebook } from 'ng2-cordova-oauth/provider/facebook';
        import { Config } from '../../../config';
        interface ILoginResponse {
            access_token: string;
        }
        @Injectable()
        export class FacebookOauthProvider implements IOathProvider {
            private cordovaOauth: CordovaOauth;
            private http: Http;
            private config: Config;
            private facebook: Facebook;
        constructor(http: Http, config: Config) {
                this.http = http;
                this.config = config;
                this.facebook = new Facebook({ clientId: config.facebook.appId, appScope: config.facebook.scope });
                this.cordovaOauth = new CordovaOauth();
            }
        login(): Promise {
                return this.cordovaOauth.login(this.facebook)
                    .then((x: ILoginResponse) => x.access_token);
            }
        }
    

    Similarly, using the CordovaOauth object available by the ng2-cordova-oauth library, we will implement the Google authentication provider with its own login() function. However, here we pass another clientId from Config that corresponds to the application we configured with Google using the Google Developer Console.
    Therefore, create a google-oauth.provider.ts file and paste the following lines:

    import { Injectable } from '@angular/core';
    import { IOathProvider } from '../oauth.provider.interface';
    import { OAuthProfile } from '../models/oauth-profile.model';
    import { CordovaOauth } from 'ng2-cordova-oauth/oauth';
    import { Google } from 'ng2-cordova-oauth/provider/google';
    import { Config } from '../../../config';
    import { Http } from '@angular/http';
    interface ILoginResponse {
        access_token: string;
    }
    @Injectable()
    export class GoogleOauthProvider implements IOathProvider {
        private http: Http;
        private config: Config;
        private cordovaOauth: CordovaOauth;
        private google: Google;
    constructor(http: Http, config: Config) {
            this.http = http;
            this.config = config;
            this.google = new Google({ clientId: config.google.appId, appScope: config.google.scope });
            this.cordovaOauth = new CordovaOauth();
        }
    login(): Promise {
            return this.cordovaOauth.login(this.google).then((x: ILoginResponse) => x.access_token);
        }
    getProfile(accessToken: string): Promise {
            let query = `access_token=${accessToken}`;
            let url = `${this.config.google.apiUrl}userinfo?${query}`;
    return this.http.get(url)
                .map(x => x.json())
                .map(x => {
                    let name = x.name.split(' ');
                    return {
                        firstName: name[0],
                        lastName: name[1],
                        email: x.email,
                        provider: 'google'
                    };
                })
                .toPromise();
        }
    }
    

    Full credits to this Article and you can find the Working Code in Github. I haven’t covered the whole Tutorial , only the parts (Google and Facebook) of that tutorial .i.e. What plugin we need to install and how to use using the TypeScript , if you need beyond that then you can refer to that tutorial

    Login or Signup to reply.
  2. To secure data in Database you have to use any of the ‘hashing’ algorithms. I recommend to use HMAC SHA 256 algorithm. Hashing can’t able to do decrypt the data. To do the hashing you need the key, you can make the key as either static or dynamic based on your need. Store the encrypted key in Database by using any of the RSA encryption algorithms and while hashing you can decrypt the key and pass in hashing method.

    To do Hashing use apache HmacUtils.

    String hashedPassword = HmacUtils.hmacSha1Hex(password,passwordkey);
    

    To do encryption and decryption Please use below tutorial RSA Encryption and Decryption

    Login or Signup to reply.
  3. Examples from one of my projects. Java Client for JWT Authentication:

    import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    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.web.client.RestTemplate;
    
    import java.util.Map;
    
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
    
        public MicroserviceSecurityConfiguration() {
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .csrf()
                .disable()
                .headers()
                .frameOptions()
                .disable()
            .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests()
                .antMatchers("/api/profile-info").permitAll()
                .antMatchers("/api/**").authenticated()
                .antMatchers("/management/health").permitAll()
                .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/swagger-resources/configuration/ui").permitAll();
        }
    
        @Bean
        public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
            return new JwtTokenStore(jwtAccessTokenConverter);
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(
                @Qualifier("loadBalancedRestTemplate") RestTemplate keyUriRestTemplate) {
    
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setVerifierKey(getKeyFromAuthorizationServer(keyUriRestTemplate));
            return converter;
        }
    
        @Bean
        public RestTemplate loadBalancedRestTemplate(RestTemplateCustomizer customizer) {
            RestTemplate restTemplate = new RestTemplate();
            customizer.customize(restTemplate);
            return restTemplate;
        }
    
        private String getKeyFromAuthorizationServer(RestTemplate keyUriRestTemplate) {
            HttpEntity<Void> request = new HttpEntity<Void>(new HttpHeaders());
            return (String) keyUriRestTemplate
                .exchange("http://someserver/oauth/token_key", HttpMethod.GET, request, Map.class).getBody()
                .get("value");
    
        }
    }
    

    Java backend for social buttons including (Google and Facebook):

    import package.repository.SocialUserConnectionRepository;
    import package.repository.CustomSocialUsersConnectionRepository;
    import package.security.social.CustomSignInAdapter;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    import org.springframework.social.UserIdSource;
    import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
    import org.springframework.social.config.annotation.EnableSocial;
    import org.springframework.social.config.annotation.SocialConfigurer;
    import org.springframework.social.connect.ConnectionFactoryLocator;
    import org.springframework.social.connect.ConnectionRepository;
    import org.springframework.social.connect.UsersConnectionRepository;
    import org.springframework.social.connect.web.ConnectController;
    import org.springframework.social.connect.web.ProviderSignInController;
    import org.springframework.social.connect.web.ProviderSignInUtils;
    import org.springframework.social.connect.web.SignInAdapter;
    import org.springframework.social.facebook.connect.FacebookConnectionFactory;
    import org.springframework.social.google.connect.GoogleConnectionFactory;
    import org.springframework.social.security.AuthenticationNameUserIdSource;
    import org.springframework.social.twitter.connect.TwitterConnectionFactory;
    // jhipster-needle-add-social-connection-factory-import-package
    
    import javax.inject.Inject;
    
    /**
     * Basic Spring Social configuration.
     *
     * <p>Creates the beans necessary to manage Connections to social services and
     * link accounts from those services to internal Users.</p>
     */
    @Configuration
    @EnableSocial
    public class SocialConfiguration implements SocialConfigurer {
        private final Logger log = LoggerFactory.getLogger(SocialConfiguration.class);
    
        @Inject
        private SocialUserConnectionRepository socialUserConnectionRepository;
    
        @Inject
        Environment environment;
    
        @Bean
        public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,
                ConnectionRepository connectionRepository) {
    
            ConnectController controller = new ConnectController(connectionFactoryLocator, connectionRepository);
            controller.setApplicationUrl(environment.getProperty("spring.application.url"));
            return controller;
        }
    
        @Override
        public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
            // Google configuration
            String googleClientId = environment.getProperty("spring.social.google.clientId");
            String googleClientSecret = environment.getProperty("spring.social.google.clientSecret");
            if (googleClientId != null && googleClientSecret != null) {
                log.debug("Configuring GoogleConnectionFactory");
                connectionFactoryConfigurer.addConnectionFactory(
                    new GoogleConnectionFactory(
                        googleClientId,
                        googleClientSecret
                    )
                );
            } else {
                log.error("Cannot configure GoogleConnectionFactory id or secret null");
            }
    
            // Facebook configuration
            String facebookClientId = environment.getProperty("spring.social.facebook.clientId");
            String facebookClientSecret = environment.getProperty("spring.social.facebook.clientSecret");
            if (facebookClientId != null && facebookClientSecret != null) {
                log.debug("Configuring FacebookConnectionFactory");
                connectionFactoryConfigurer.addConnectionFactory(
                    new FacebookConnectionFactory(
                        facebookClientId,
                        facebookClientSecret
                    )
                );
            } else {
                log.error("Cannot configure FacebookConnectionFactory id or secret null");
            }
    
            // Twitter configuration
            String twitterClientId = environment.getProperty("spring.social.twitter.clientId");
            String twitterClientSecret = environment.getProperty("spring.social.twitter.clientSecret");
            if (twitterClientId != null && twitterClientSecret != null) {
                log.debug("Configuring TwitterConnectionFactory");
                connectionFactoryConfigurer.addConnectionFactory(
                    new TwitterConnectionFactory(
                        twitterClientId,
                        twitterClientSecret
                    )
                );
            } else {
                log.error("Cannot configure TwitterConnectionFactory id or secret null");
            }
    
            // jhipster-needle-add-social-connection-factory
        }
    
        @Override
        public UserIdSource getUserIdSource() {
            return new AuthenticationNameUserIdSource();
        }
    
        @Override
        public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
            return new CustomSocialUsersConnectionRepository(socialUserConnectionRepository, connectionFactoryLocator);
        }
    
        @Bean
        public SignInAdapter signInAdapter() {
            return new CustomSignInAdapter();
        }
    
        @Bean
        public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository, SignInAdapter signInAdapter) throws Exception {
            ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, signInAdapter);
            providerSignInController.setSignUpUrl("/social/signup");
            providerSignInController.setApplicationUrl(environment.getProperty("spring.application.url"));
            return providerSignInController;
        }
    
        @Bean
        public ProviderSignInUtils getProviderSignInUtils(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
            return new ProviderSignInUtils(connectionFactoryLocator, usersConnectionRepository);
        }
    }
    
    package package.repository;
    
    import package.domain.SocialUserConnection;
    
    import org.springframework.social.connect.*;
    
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    public class CustomSocialUsersConnectionRepository implements UsersConnectionRepository {
    
        private SocialUserConnectionRepository socialUserConnectionRepository;
    
        private ConnectionFactoryLocator connectionFactoryLocator;
    
        public CustomSocialUsersConnectionRepository(SocialUserConnectionRepository socialUserConnectionRepository, ConnectionFactoryLocator connectionFactoryLocator) {
            this.socialUserConnectionRepository = socialUserConnectionRepository;
            this.connectionFactoryLocator = connectionFactoryLocator;
        }
    
        @Override
        public List<String> findUserIdsWithConnection(Connection<?> connection) {
            ConnectionKey key = connection.getKey();
            List<SocialUserConnection> socialUserConnections =
                socialUserConnectionRepository.findAllByProviderIdAndProviderUserId(key.getProviderId(), key.getProviderUserId());
            return socialUserConnections.stream()
                .map(SocialUserConnection::getUserId)
                .collect(Collectors.toList());
        };
    
        @Override
        public Set<String> findUserIdsConnectedTo(String providerId, Set<String> providerUserIds) {
            List<SocialUserConnection> socialUserConnections =
                socialUserConnectionRepository.findAllByProviderIdAndProviderUserIdIn(providerId, providerUserIds);
            return socialUserConnections.stream()
                .map(SocialUserConnection::getUserId)
                .collect(Collectors.toSet());
        };
    
        @Override
        public ConnectionRepository createConnectionRepository(String userId) {
            if (userId == null) {
                throw new IllegalArgumentException("userId cannot be null");
            }
            return new CustomSocialConnectionRepository(userId, socialUserConnectionRepository, connectionFactoryLocator);
        };
    }
    
    
    package package.security.social;
    
    import package.config.JHipsterProperties;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.social.connect.Connection;
    import org.springframework.social.connect.web.SignInAdapter;
    import org.springframework.web.context.request.NativeWebRequest;
    
    import javax.inject.Inject;
    
    public class CustomSignInAdapter implements SignInAdapter {
    
        @SuppressWarnings("unused")
        private final Logger log = LoggerFactory.getLogger(CustomSignInAdapter.class);
    
        @Inject
        private UserDetailsService userDetailsService;
    
        @Inject
        private JHipsterProperties jHipsterProperties;
    
        @Override
        public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {
            UserDetails user = userDetailsService.loadUserByUsername(userId);
            Authentication newAuth = new UsernamePasswordAuthenticationToken(
                user,
                null,
                user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(newAuth);
            return jHipsterProperties.getSocial().getRedirectAfterSignIn();
        }
    }
    

    Example for JWT only

    import io.github.jhipster.config.JHipsterProperties;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.social.connect.Connection;
    import org.springframework.social.connect.web.SignInAdapter;
    import org.springframework.web.context.request.NativeWebRequest;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.web.context.request.ServletWebRequest;
    import javax.servlet.http.Cookie;
    
    public class CustomSignInAdapter implements SignInAdapter {
    
        @SuppressWarnings("unused")
        private final Logger log = LoggerFactory.getLogger(CustomSignInAdapter.class);
    
        private final UserDetailsService userDetailsService;
    
        private final JHipsterProperties jHipsterProperties;
    
        private final TokenProvider tokenProvider;
    
    
        public CustomSignInAdapter(UserDetailsService userDetailsService, JHipsterProperties jHipsterProperties,
                TokenProvider tokenProvider) {
            this.userDetailsService = userDetailsService;
            this.jHipsterProperties = jHipsterProperties;
            this.tokenProvider = tokenProvider;
        }
    
        @Override
        public String signIn(String userId, Connection<?> connection, NativeWebRequest request){
            try {
                UserDetails user = userDetailsService.loadUserByUsername(userId);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    user,
                    null,
                    user.getAuthorities());
    
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                String jwt = tokenProvider.createToken(authenticationToken, false);
                ServletWebRequest servletWebRequest = (ServletWebRequest) request;
                servletWebRequest.getResponse().addCookie(getSocialAuthenticationCookie(jwt));
            } catch (AuthenticationException ae) {
                log.error("Social authentication error");
                log.trace("Authentication exception trace: {}", ae);
            }
            return jHipsterProperties.getSocial().getRedirectAfterSignIn();
        }
    
        private Cookie getSocialAuthenticationCookie(String token) {
            Cookie socialAuthCookie = new Cookie("social-authentication", token);
            socialAuthCookie.setPath("/");
            socialAuthCookie.setMaxAge(10);
            return socialAuthCookie;
        }
    }
    

    JHipsterProperties is just layer before simple Properties configuration.
    This is generated by JHipster. You could generate monolithic application and see examples how backend and frontend should work to enable social buttons.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search