9.5. Social Login
Social login is a form of single sign-on that allows you to use credentials from a social networking service such as Facebook, Twitter or Google+, to sign into CUBA application instead of creating a new login account explicitly for the application.
In the following example we will consider the social login using Facebook. Facebook uses 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 the social login implementation.
-
In order 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
app.properties
file of the core module as thefacebook.appId
andfacebook.appSecret
application properties respectively, for example:facebook.appId = 123456789101112 facebook.appSecret = 123456789101112abcde131415fghi16
Also 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
-
Extend the login screen and add the button for social login. This button will invoke the
loginFacebook()
method - the entry point to the social login flow. -
To use Facebook user accounts, we need to add one extra field to the standard CUBA user account. Extend the
User
entity and add thefacebookId
attribute of String type to it:@Column(name = "FACEBOOK_ID") protected String facebookId;
-
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 Metadata metadata; @Inject private Persistence persistence; @Inject private Configuration configuration; @Override @Transactional public User findOrRegisterUser(String facebookId, String email, String name) { EntityManager em = persistence.getEntityManager(); TypedQuery<SocialUser> query = em.createQuery("select u from sec$User u where u.facebookId = :facebookId", SocialUser.class); query.setParameter("facebookId", facebookId); query.setViewName(View.LOCAL); SocialUser existingUser = query.getFirstResult(); if (existingUser != null) { return existingUser; } SocialRegistrationConfig config = configuration.getConfig(SocialRegistrationConfig.class); Group defaultGroup = em.find(Group.class, config.getDefaultGroupId(), View.MINIMAL); SocialUser user = metadata.create(SocialUser.class); user.setFacebookId(facebookId); user.setEmail(email); user.setName(name); user.setGroup(defaultGroup); user.setActive(true); user.setLogin(email); em.persist(user); return user; } }
-
Create a service to manage the login process. In this example it is FacebookService that contains two methods:
getLoginUrl()
andgetUserData()
.-
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 get the user’s id, name and email, the id will correspond thefacebookId
attribute created above.
-
-
Define the
facebook.fields
andfacebook.scope
application properties in theapp.properties
file of the core module:facebook.fields = id,name,email facebook.scope = email
-
Go back to the
loginFacebook()
method in the controller of the extended login window. The full code of the controller you can find in the ExtAppLoginWindow.java file.In this method 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; public void loginFacebook() { VaadinSession.getCurrent() .addRequestHandler(facebookCallBackRequestHandler); this.redirectUri = Page.getCurrent().getLocation(); String loginUrl = facebookService.getLoginUrl(globalConfig.getWebAppUrl(), OAuth2ResponseType.CODE); Page.getCurrent() .setLocation(loginUrl); }
The
handleFacebookCallBackRequest()
method will handle the callback after Facebook authentication form. Firstly, we use theUIAccessor
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 byfacebookId
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"); FacebookUserData userData = facebookService.getUserData(globalConfig.getWebAppUrl(), code); User user = socialRegistrationService.findOrRegisterUser( userData.getId(), userData.getEmail(), userData.getName()); App app = App.getInstance(); Connection connection = app.getConnection(); Locale defaultLocale = messages.getTools().getDefaultLocale(); ((ExternallyAuthenticatedConnection) connection) .loginAfterExternalAuthentication(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; }
-
The last step is to disable the built-in LDAP authentication mechanism in your project. Create an implementation of
CubaAuthProvider
in theauth
package under the root package of the web module and simply override the following methods making no changes in them:public class NoOpAuthProvider implements CubaAuthProvider { @Override public void authenticate(String login, String password, Locale messagesLocale) throws LoginException { } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { } @Override public void destroy() { } }
Register this implementation in
web-app.properties
file in the web module:web-app.propertiescuba.web.externalAuthenticationProviderClass = com.company.demo.web.auth.NoOpAuthProvider
Now, when a user clicks the Facebook button on the login screen, the application will ask him for permission to use his Facebook profile and email, and if this permission is granted, the logged in user will be redirected to the main application page.