Social Login
Jmix offers a comprehensive security subsystem that includes user management features out of the box. This built-in functionality allows developers to implement a variety of authentication methods while also providing the flexibility to integrate custom solutions. Among these options, Jmix supports integration with third-party services, such as LDAP/Active Directory servers and OpenID Connect providers.
One notable feature is the ability to implement Social Login, which enables users to authenticate using their existing accounts from popular platforms like GitHub, Facebook, and Google. This approach not only streamlines the registration process for users but also simplifies user management for developers and administrators.
Most Social Login services operate using widely adopted protocols such as OAuth and OpenID Connect, ensuring secure and efficient authentication. This guide will walk you through the steps to implement Social Login in your Jmix applications.
For applications that need to integrate with standards-compliant services or on-premises software like Keycloak, consider also using the OpenID Connect add-on for your Jmix project. |
Requirements
To effectively use this guide, you will need the following:
-
Setup the development environment.
-
If you don’t want to follow the guide step-by-step, you can download or clone the completed sample project:
git clone https://github.com/jmix-framework/jmix-social-login-sample.git
This will allow you to explore the finished implementation and experiment with the functionality right away.
What We Are Going to Build
This guide outlines the process of enhancing an application created using the standard Full-Stack Application (Java) template to support authentication via Google OpenID Connect and GitHub OAuth, alongside the default database-hosted accounts.
Users will have the flexibility to select their preferred authentication method directly from the login screen.
The implementation will be based on the Spring Security OAuth 2.0 Login feature.
Configure Authentication Providers
To enable authentication with a 3rd party service you will have to register Client ID and Client Secret in that service.
Never expose your Client Secret in client-side code or public repositories. |
GitHub OAuth
-
Go to Developer Settings page of your GitHub profile.
-
Sign in to your GitHub account.
-
Click on your profile picture in the upper-right corner and select Settings from the dropdown menu.
-
In the left sidebar, scroll down to Developer settings and click it.
-
On the Developer settings page, click on OAuth Apps.
-
-
Create a new OAuth App
-
Click on the New OAuth App button to start configuring a new application.
-
Fill in the registration form with the following details:
-
Application name: Choose a name that represents your application.
-
Homepage URL: Enter
http://localhost:8080
if you are running the app locally, or provide the actual application’s base URL if it’s hosted on a server. -
Authorization callback URL: Use
http://localhost:8080/login/oauth2/code/github
if running locally. Replacehttp://localhost:8080
with the actual application’s base URL if it’s hosted on a server.
-
-
Click Register application.
-
-
Generate Client Secret
-
In the newly created app settings, click the Generate a new client secret button.
-
Copy both the
Client ID
andClient secret
that were generated. These credentials will be required to configure OAuth for your application.
-
-
Securely store the
Client ID
andClient secret
in a safe location, as you’ll need them for integrating GitHub OAuth authentication with your application.
Google OpenID
-
Open Google Cloud Console and sign in with your Google account if you aren’t already logged in.
-
Create a New Project
-
In the top navigation bar, click on the Project dropdown.
-
Select New Project and enter a project name.
-
Enter a project name.
-
Click Create to initialize your project.
-
-
Enable APIs & Services
-
With your project selected, go to APIs & Services.
-
Select OAuth consent screen to begin setting up OAuth for your app.
-
-
Configure the OAuth Consent Screen
-
Choose the External option for the user type and click Create.
-
Fill in the required fields in the form (App name, Support email, etc.), then click Save and Continue.
-
-
Add Scopes
-
In the Scopes section, click Add or Remove Scopes.
-
Add the following scopes:
-
…/auth/userinfo.email
-
…/auth/userinfo.profile
-
-
Click Update to add these scopes, then proceed by clicking Save and Continue.
-
-
Complete the OAuth Consent Screen
-
Review the information on the final page of the consent screen setup.
-
Click Back to Dashboard to save and complete the consent screen configuration.
-
-
Create OAuth Credentials
-
Go to the Credentials section in the left sidebar.
-
Click Create Credentials and select OAuth Client ID.
-
-
Configure the OAuth Client ID
-
Choose Web Application as the application type.
-
In the Authorized redirect URIs section, add the following URI:
http://localhost:8080/login/oauth2/code/google
. Replacehttp://localhost:8080
with your app’s actual base URL if it is hosted on a server.
-
-
Generate the Client ID and Client Secret
-
Click Create to finish.
-
A dialog box will appear displaying the
Client ID
andClient secret
. Copy these credentials as they are essential for configuring OpenID authentication in your application.
-
Configure Application Project
Specify client ids and secrets in the project’s application.properties
file:
spring.security.oauth2.client.registration.google.client-id=<your-google-id>
spring.security.oauth2.client.registration.google.client-secret=<your-google-secret>
spring.security.oauth2.client.registration.github.client-id=<your-github-id>
spring.security.oauth2.client.registration.github.client-secret=<your-github-secret>
Add Spring Boot OAuth2 client starter to the build.gradle
dependencies section:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
Extend User Entity
In order to be compatible with Spring Security OAuth2 API, the User
entity should implement the org.springframework.security.oauth2.core.oidc.user.OidcUser
interface.
Add it to the list of interfaces implemented by User
:
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
// ...
public class User implements JmixUserDetails, HasTimeZone, OidcUser {
// ...
@Override
public OidcUserInfo getUserInfo() {
return null;
}
@Override
public OidcIdToken getIdToken() {
return null;
}
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public Map<String, Object> getClaims() {
return null;
}
The OidcUser
methods are not used in the application, so you can leave their implementations empty.
Configure OAuth2 Login
To customize OAuth 2.0 Login provided by Spring Security, you need to create a security configuration class. It should extend the FlowuiVaadinWebSecurity
class defined in Jmix and be annotated with @EnableWebSecurity
:
@EnableWebSecurity
@Configuration
public class OAuth2SecurityConfiguration extends FlowuiVaadinWebSecurity {
@Autowired
private RoleGrantedAuthorityUtils authorityUtils;
@Autowired
private UnconstrainedDataManager dataManager;
// ...
The UnconstrainedDataManager
bean should be used instead of the DataManager
to allow the configuration code to access the User
entity without any restrictions.
Let’s take a closer look at the different parts of the configuration.
The configure(HttpSecurity http)
method is an entry point to the security settings configuration. It defines the OAuth2 login process by setting the login page, handling user data from the authentication provider, and managing the post-login behavior:
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.oauth2Login(configurer ->
configurer
.loginPage(getLoginPath())
.userInfoEndpoint(userInfoEndpointConfig ->
userInfoEndpointConfig
.userService(oauth2UserService())
.oidcUserService(oidcUserService()))
.successHandler(this::onAuthenticationSuccess)
);
}
The onAuthenticationSuccess()
method handles successful authentication. It redirects users to the main view after successful login:
private void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
// Redirect to the main view after successful authentication
new DefaultRedirectStrategy().sendRedirect(request, response, "/");
}
The oauth2UserService()
and oidcUserService()
methods are responsible for mapping user information returned by the authentication service to your application’s User
entity.
The oidcUserService()
method handles GitHub users:
// Returns a method that loads GitHub users
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
return (userRequest) -> {
// Delegate to the default implementation to load an external user
OAuth2User oAuth2User = delegate.loadUser(userRequest);
// Find or create a user with username corresponding to the GitHub ID
Integer githubId = oAuth2User.getAttribute("id");
User jmixUser = loadUserByUsername("github:" + githubId);
// Update the user with information from GitHub
jmixUser.setEmail(oAuth2User.getAttribute("email"));
String nameAttr = oAuth2User.getAttribute("name");
if (nameAttr != null) {
int idx = nameAttr.indexOf(" ");
if (idx > 0) {
jmixUser.setFirstName(nameAttr.substring(0, idx));
jmixUser.setLastName(nameAttr.substring(idx + 1));
} else {
jmixUser.setLastName(nameAttr);
}
}
// Save the user to the database and assign roles
User savedJmixUser = dataManager.save(jmixUser);
savedJmixUser.setAuthorities(getDefaultGrantedAuthorities());
return savedJmixUser;
};
}
The oidcUserService()
method handles Google users:
// Returns a method that loads Google users
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation to load an external user
OidcUser oidcUser = delegate.loadUser(userRequest);
// Find or create a user with username corresponding to the Google ID
String googleId = oidcUser.getSubject();
User jmixUser = loadUserByUsername("google:" + googleId);
// Update the user with information from Google
jmixUser.setEmail(oidcUser.getEmail());
jmixUser.setFirstName(oidcUser.getAttribute("given_name"));
jmixUser.setLastName(oidcUser.getAttribute("family_name"));
// Update the user to the database and assign roles
User savedJmixUser = dataManager.save(jmixUser);
savedJmixUser.setAuthorities(getDefaultGrantedAuthorities());
return savedJmixUser;
};
}
Both methods depend on the loadUserByUsername()
function that loads a user by username from the database or creates a new user if the user does not exist:
// Loads user by username or creates a new user
private User loadUserByUsername(String username) {
return dataManager.load(User.class)
.query("e.username = ?1", username)
.optional()
.orElseGet(() -> {
User user = dataManager.create(User.class);
user.setUsername(username);
return user;
});
}
The username
attribute of the User
entity will store identifiers returned by the authentication services.
The getDefaultGrantedAuthorities()
method creates a list of authorities to be assigned to the authenticated user. For demonstration purposes, the getDefaultGrantedAuthorities()
method assigns full access rights. In a real-world application, however, it is essential to assign more limited privileges to new users. At minimum, new registrations should be assigned a ui-minimal
role, as well as user-specific roles that provide access to relevant business entities, attributes, views, and menu items.
// Builds granted authority list to assign default roles to the user
private Collection<GrantedAuthority> getDefaultGrantedAuthorities() {
return List.of(
authorityUtils.createResourceRoleGrantedAuthority(FullAccessRole.CODE)
);
}
Modify Login View
Open login-view.xml
descriptor and add login buttons below the loginForm
element:
<h3 text="msg://otherLogin.text" classNames="other-login-header"/>
<vbox classNames="login-wrapper">
<button id="googleBtn"
text="Sign in with Google"
width="100%"
icon="app-icons:google"
disableOnClick="true"
classNames="google-style, other-login-button"/>
<button id="githubBtn"
text="Sign in with GitHub"
width="100%"
icon="app-icons:github"
disableOnClick="true"
classNames="github-style, other-login-button"/>
</vbox>
The button click handlers should redirect to URLs corresponding to the authentication providers:
@Subscribe(id = "googleBtn", subject = "clickListener")
public void onGoogleBtnClick(final ClickEvent<JmixButton> event) {
UI.getCurrent().getPage().setLocation("/oauth2/authorization/google");
}
@Subscribe(id = "githubBtn", subject = "clickListener")
public void onGithubBtnClick(final ClickEvent<JmixButton> event) {
UI.getCurrent().getPage().setLocation("/oauth2/authorization/github");
}
Add some styling to /frontend/themes/sample-social-login/sample-social-login.css
file to have nice looking social login buttons:
.login-wrapper {
padding: var(--lumo-space-l);
max-width: calc(var(--lumo-size-m) * 10);
background: var(--lumo-base-color) linear-gradient(var(--lumo-tint-5pct), var(--lumo-tint-5pct));
}
.other-login-header {
color: var(--lumo-contrast-80pct);
width: calc(var(--lumo-size-m) * 10);
overflow: hidden;
text-align: center;
}
.other-login-header::before {
content: '';
display: inline-block;
border-bottom: 1px solid;
color: var(--lumo-contrast-40pct);
width: 50%;
margin: 0 0.5em 0 -55%;
vertical-align: middle;
}
.other-login-header::after {
content: '';
display: inline-block;
border-bottom: 1px solid;
color: var(--lumo-contrast-40pct);
margin: 0 -55% 0 0.5em;
vertical-align: middle;
width: 50%;
}
.other-login-button {
color: var(--lumo-contrast-80pct);
}
.google-style vaadin-icon {
color: #4285F4;
width: var(--lumo-icon-size-s);
height: var(--lumo-icon-size-s);
}
.github-style vaadin-icon {
color: black;
width: var(--lumo-icon-size-s);
height: var(--lumo-icon-size-s);
}
This must be enough to test the implemented authentication methods. Run the application and open http://localhost:8080 in the incognito browser tab.