6.7. Social Login

A social login is a form of single sign-on that allows you to use credentials from a social networking site such as Facebook, Twitter or Google+, to sign in to a CUBA application instead of creating a new login account explicitly for the application.

See Anonymous Access & Social Login guide to learn how to set up public access to some application screens and implement a custom login using a Google, Facebook, or GitHub account.

In the following example, we will consider the social login using Facebook. Facebook uses the OAuth2 authorization mechanism; for more details, consult the documentation on Facebook API and Facebook Login Flow: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow.

The source code of this sample project is available on GitHub. Below are the key points of social login implementation.

  1. To connect your application to Facebook, you will need to create the App ID (unique application identifier) and App Secret (a kind of password that authenticates requests from your application to Facebook servers). Follow the instruction and register the generated values in the app.properties file of the core module as the facebook.appId and facebook.appSecret application properties, respectively, for example:

    facebook.appId = 123456789101112
    facebook.appSecret = 123456789101112abcde131415fghi16

    Grant email permission that allows your app to view the user’s primary email address.

    Then, register the URL that you used for the Facebook app registration in the cuba.webAppUrl property in the core and web application properties files, for example:

    cuba.webAppUrl = http://cuba-fb.test:8080/app
  2. Extend the login screen and add the button for the social login. Subscribe to the button click event: it will be the entry point to the social login flow.

    <linkButton id="facebookBtn"
                align="MIDDLE_CENTER"
                caption="Facebook"
                icon="font-icon:FACEBOOK_SQUARE"/>
  3. We need to add one extra field to the standard CUBA user account to use Facebook user accounts. Extend the User entity and add the facebookId attribute of String type to it:

    @Column(name = "FACEBOOK_ID")
    protected String facebookId;
  4. Create a FacebookAccessRole role that allows the user to view the Help, Settings, and About screens:

    @Role(name = "facebook-access")
    public class FacebookAccessRole extends AnnotatedRoleDefinition {
        @ScreenAccess(screenIds = {
                "help",
                "aboutWindow",
                "settings",
        })
        @Override
        public ScreenPermissionsContainer screenPermissions() {
            return super.screenPermissions();
        }
    }
  5. Create a service that will look for a user with a given facebookId in the application database and either return the existent user or create a new one:

    public interface SocialRegistrationService {
        String NAME = "demo_SocialRegistrationService";
    
        User findOrRegisterUser(String facebookId, String email, String name);
    }
    @Service(SocialRegistrationService.NAME)
    public class SocialRegistrationServiceBean implements SocialRegistrationService {
    
        @Inject
        private DataManager dataManager;
        @Inject
        private Configuration configuration;
    
        @Override
        public User findOrRegisterUser(String facebookId, String email, String name) {
            User existingUser = dataManager.load(User.class)
                    .query("select u from sec$User u where u.facebookId = :facebookId")
                    .parameter("facebookId", facebookId)
                    .optional()
                    .orElse(null);
            if (existingUser != null) {
                return existingUser;
            }
            SocialUser user = dataManager.create(SocialUser.class);
            user.setLogin(email);
            user.setName(name);
            user.setGroup(getDefaultGroup());
            user.setActive(true);
            user.setEmail(email);
            user.setFacebookId(facebookId);
            UserRole fbUserRole = dataManager.create(UserRole.class);
            fbUserRole.setRoleName("facebook-access");
            fbUserRole.setUser(user);
            EntitySet eSet = dataManager.commit(user, fbUserRole);
            return eSet.get(user);
        }
    
        private Group getDefaultGroup() {
            SocialRegistrationConfig config = configuration.getConfig(SocialRegistrationConfig.class);
    
            return dataManager.load(Group.class)
                    .query("select g from sec$Group g where g.id = :defaultGroupId")
                    .parameter("defaultGroupId", config.getDefaultGroupId())
                    .one();
        }
    }
  6. Create a service to manage the login process. In this example it is FacebookService that contains two methods: getLoginUrl() and getUserData().

    • getLoginUrl() will generate the login URL based on the application URL and OAuth2 response type (code, access token, or both; see more on response types in Facebook API documentation). The complete implementation of this method you can find in the FacebookServiceBean.java file.

    • getUserData() will look for the Facebook user by the given application URL and code and either return the personal data of the existent user or create a new one. In this example, we want to get the user’s id, name, and email; the id will correspond the facebookId attribute created above.

  7. Define the facebook.fields application property in the app.properties file of the core module:

    facebook.fields = id,name,email
  8. Go back to the Facebook login button click event in the controller of the extended login screen. The full code of the controller you can find in the ExtLoginScreen.java file.

    In this event, we add the request handler to the current session, save the current URL, and invoke the redirect to the Facebook authentication form in the browser:

    private RequestHandler facebookCallBackRequestHandler =
            this::handleFacebookCallBackRequest;
    
    private URI redirectUri;
    
    @Inject
    private FacebookService facebookService;
    
    @Inject
    private GlobalConfig globalConfig;
    
    @Subscribe("facebookBtn")
    public void onFacebookBtnClick(Button.ClickEvent event) {
        VaadinSession.getCurrent()
            .addRequestHandler(facebookCallBackRequestHandler);
    
        this.redirectUri = Page.getCurrent().getLocation();
    
        String loginUrl = facebookService.getLoginUrl(globalConfig.getWebAppUrl(), FacebookService.OAuth2ResponseType.CODE);
        Page.getCurrent()
            .setLocation(loginUrl);
    }

    The handleFacebookCallBackRequest() method will handle the callback after the Facebook authentication form. Firstly, we use the UIAccessor instance to lock the UI until the login request is proceeded.

    Then, FacebookService will get the Facebook user account email and id. After that, the corresponding CUBA user will be found by facebookId or registered on the fly in the system.

    Next, the authentication is triggered, the user session on behalf of this user is loaded, and the UI is updated. After that, we remove the Facebook callback handler, as far as we no longer expect authentication.

    public boolean handleFacebookCallBackRequest(VaadinSession session, VaadinRequest request,
                                                 VaadinResponse response) throws IOException {
        if (request.getParameter("code") != null) {
            uiAccessor.accessSynchronously(() -> {
                try {
                    String code = request.getParameter("code");
    
                    FacebookService.FacebookUserData userData = facebookService.getUserData(globalConfig.getWebAppUrl(), code);
    
                    User user = socialRegistrationService.findOrRegisterUser(
                            userData.getId(), userData.getEmail(), userData.getName());
    
                    Connection connection = app.getConnection();
    
                    Locale defaultLocale = messages.getTools().getDefaultLocale();
                    connection.login(new ExternalUserCredentials(user.getLogin(), defaultLocale));
                } catch (Exception e) {
                    log.error("Unable to login using Facebook", e);
                } finally {
                    session.removeRequestHandler(facebookCallBackRequestHandler);
                }
            });
    
            ((VaadinServletResponse) response).getHttpServletResponse().
                    sendRedirect(ControllerUtils.getLocationWithoutParams(redirectUri));
    
            return true;
        }
    
        return false;
    }

Now, when a user clicks the Facebook button on the login screen, the application will ask them for permission to use their Facebook profile and email, and if this permission is granted, the logged-in user will be redirected to the main application page.

You can implement your own login mechanism using custom LoginProvider, HttpRequestFilter, or events described in the Web Login section.