Lots of webmasters love to give their users the option to login with either Facebook or Twitter. Fortunately, you can add social media login support to your Spring Boot app.

That’s because Spring offers a project called Spring Social that enables you to add social media login options to your app. Even better: those login solutions integrate nicely with Spring Security.

Wait a second. Didn’t we already cover a Facebook login solution and a Twitter login solution?

Why, yes. Yes we did. But those solutions didn’t integrate with Spring Security. They used external libraries and are suitable options if, for whatever reason, you don’t want to use Spring Security.

In this article, we’ll go over how to enable your users to login with either Facebook or Twitter. If you want to see the complete code for this project, have a look on GitHub.

 

Create the Apps

Before you can offer your users a Facebook/Twitter login solution, you need to create an application for both platforms.

There’s no need to reinvent the wheel. We’ve already covered how to create a Facebook app and a Twitter app in the past. Feel free to browse those articles if you need to.

 

Set the Properties

Once you’ve created your social media applications, you need to give your Spring Boot app permission to communicate with them. You’ll do that by adding properties in the application.properties file.

Behold:

1
2
3
4
spring.social.facebook.appId=[your Facebook app ID]
spring.social.facebook.appSecret=[your Facebook app secret]
spring.social.twitter.appId=[your Twitter app ID[
spring.social.twitter.appSecret=[your Twitter app secret]

Obviously, you’ll want to update the code above with your own credentials.

 

Add the Necessary Dependencies

You’re going to need quite a few dependencies for this one. Expect your JAR file footprint to be large.

For starters, you’ll need the Spring Social dependencies for both Facebook and Twitter. In addition to that, you’ll need related Spring Security dependencies as well.

Here’s what the list of dependencies should look like your POM file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.social</groupId>
        <artifactId>spring-social-facebook</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.social</groupId>
        <artifactId>spring-social-twitter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 

Create the User Class

Next, create a User class to represent the user that logs in via Facebook or Twitter.

Keep it simple for now by just including a single field that stores the user’s display name. Later on, you can add other fields (such as address, phone number, etc.).

Here’s the very simple User class:

1
2
3
4
5
6
7
8
9
10
11
public class User {
 
    private String name;
     
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

 

Configure the MVC Views

Next, it’s time to configure a few views. For the purposes of this demo, you’ll just need 3 of them: a home page, a login page, and a successful login page.

The home page is the “front end” of your website. That’s where you market your brand, so you’ll let visitors view the home page even if they’re not logged in.

The login page allows the user to login with either Facebook or Twitter.

The successful login page is a simple “Welcome!” page that confirms to the user that the login attempt was successful.

Here’s what the code looks like for configuring the views.

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
 
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/socialloginhome").setViewName("socialloginhome");
        registry.addViewController("/").setViewName("socialloginhome");
        registry.addViewController("/socialloginsuccess").setViewName("socialloginsuccess");
        registry.addViewController("/sociallogin").setViewName("sociallogin");
    }
 
}

 

Configure Security

Fasten your seat belts. Things are about to get quite a bit more complicated.

The next step is to configure web security. You’ll do that in a class that extends WebSecurityConfigurerAdapter.

First, override the configure() method to define site-wide security. That code looks like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
            .antMatchers("/", "/socialloginhome", "/assets/**", "/images/**","/login*","/signin/**").permitAll()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/sociallogin")
            .permitAll()
            .and()
        .logout()
            .permitAll();
}

As you can see, right out of the gate the code is disabling cross-site forgery requests with the csrf().disable() code. Yes, that opens up some security issues, but it’s necessary when you need to use a third-party API.

In the authorizeRequests() block, the code is essentially establishing permissions. The first antMatchers() line specifies all the URLs that visitors can access without logging in. Note that URL wildcards like “/images/**” and “/assets/**” are included because they contain images, CSS code, and JavaScript libraries that you want all users to access regardless of login status.

The formLogin() block is fairly self-explanatory. It establishes the URL for the form login and allows all visitors to access it.

Finally, logout() is also universally allowed.

 

The ProviderSignInController

In addition to specifying HTTP security, you also need to use Spring Social’s ProviderSignInController to enable social logins. Here’s what that code looks like:

1
2
3
4
5
6
7
8
9
10
@Bean
public ProviderSignInController providerSignInController() {
    ((InMemoryUsersConnectionRepository) usersConnectionRepository)
      .setConnectionSignUp(socialConnectionSignup);
      
    return new ProviderSignInController(
      connectionFactoryLocator,
      usersConnectionRepository,
      new SocialSignInAdapter());
}

First of all notice, that the method is annotated with @Bean. That’s because the object returned by the method is a bean managed by the Spring container.

That first line of code in the method references a couple of autowired objects. In a nutshell, that line of code specifies the object that will handle user signups. That’s where you’ll instantiate the User object once the user logs in.

The next line instantiates the ProviderSignInController itself with the constructor that takes 3 parameters.

The first parameter is an autowired ConnectionFactoryLocator object. That code determines what type of ConnectionFactory (i.e., Facebook or Twitter) should be used when the user logs in.

The second parameter, a type of UserConnectionFactory, is also autowired. That object is used to manage user connections to service providers.

The third parameter is a class that you’ll have to write. It’s the bridge between this controller and the code that handles application-specific login.

Keep in mind that ProviderSignInController lives up to its name. It’s really a controller class.

 

Utility Classes

Before coding the adapter and signup object, create a couple of utility classes that will make your life easier.

First, create ConnectionHelper. That class will expose one method used to determine whether the user is logging in with Facebook or Twitter.

Here’s what that code looks like:

1
2
3
4
5
6
7
8
9
10
11
public static ConnectionType getConnectionType(Connection<?> connection) {
    Object api = connection.getApi();
     
    if (api instanceof Twitter) {
        return ConnectionType.TWITTER;
    } else if (api instanceof Facebook) {
        return ConnectionType.FACEBOOK;
    } else {
        throw new RuntimeException("Unknown API!");
    }
}

Note that the method returns ConnectionType. That’s a simple enum that identifies the connection type as either a Facebook connection or a Twitter connection.

To determine the connection type, the code grabs the API object from the Connection. Then, it just evaluates the interface and returns the appropriate value.

The next utility class you should create is UserHelper. That will populate the User object based on information gleaned from the connection.

Here’s what that code looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Component
public class UserHelper {
     
    /**
     * Instantiates a User object based on login
     *
     * @param connection
     * @return user object
     */
    public User getUser(Connection<?> connection) {
        User user = null;
 
        //get the connection type
        ConnectionType type = ConnectionHelper.getConnectionType(connection);
         
        //create a user based on API type
        if (type.equals(ConnectionType.TWITTER)) {
            user = getTwitterUser(connection);
        } else if (type.equals(ConnectionType.FACEBOOK)) {
            user = getFacebookUser(connection);
        }
         
        return user;
    }
     
     
    /**
     * Handles users who've logged in via Twitter
     */
    private User getTwitterUser(Connection<?> connection) {
        User user = new User();
        Twitter twitterApi = (Twitter)connection.getApi();
         
        String name = twitterApi.userOperations().getUserProfile().getName();
         
        user.setName(name);
         
        return user;
    }
     
     
    /**
     * Handles users who've logged in via Facebook
     */
    private User getFacebookUser(Connection<?> connection) {
        User user = new User();
        Facebook facebookApi = (Facebook)connection.getApi();
         
        String name = facebookApi.userOperations().getUserProfile().getName();
         
        user.setName(name);
         
        return user;
    }
}

The getUser() method accepts a Connection object. It uses that object to determine the connection type and then populates the User object accordingly.

Note that there are two methods for populating the User object: one for Facebook and one for Twitter. They look strikingly similar, so you might think that they can be combined.

However, the object models are different for each API. It’s best to leave them separate if you plan on getting API-specific info for your users.

 

The Adapter

Next, create the adapter class that handles social media logins. Here’s what that code should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Service
public class SocialSignInAdapter implements SignInAdapter {
     
    @Override
    public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
        //get the authentication token
        Authentication authentication = getAuthentication(localUserId, connection);
         
        //put the authentication token in the context
        SecurityContextHolder.getContext().setAuthentication(authentication);
         
        //return the "success" page
        return "/socialloginsuccess";
    }
     
     
    /**
     * Creates the token for an authentication request
     *
     * @param localUserId
     * @param connection
     * @return token for an authentication request
     */
    private Authentication getAuthentication(String localUserId, Connection<?> connection) {
        //get the roles
        List<GrantedAuthority> roles = getRoles(connection);
        
        //no need for password here because the user logged in with social media
        String password = null;
         
        //instantiate the authentication object
        Authentication authentication = new UsernamePasswordAuthenticationToken(localUserId, password, roles);
         
        //get out
        return authentication;
    }
     
     
    /**
     * Returns a List of roles. For this demo, there's only one role.
     * The role is defined by how the user logged in (Facebook or Twitter)
     *
     * @param connection
     * @return list of roles
     */
    private List<GrantedAuthority> getRoles(Connection<?> connection) {
        //start with an empty list
        List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
         
        //get the connection type
        ConnectionType type = ConnectionHelper.getConnectionType(connection);
         
        //set the role based on the type
        String role = type.toString();
         
        //add the role to the list
        roles.add(new SimpleGrantedAuthority(role));
         
        //get out
        return roles;
    }
}

The signIn() method is where everything happens. That’s the implemented method from the interface.

First, the method constructs an Authentication object. We’ll look at that code in a bit.

Next, the code adds the Authentication object to the security context. That’s where the user officially becomes part of the family.

Finally, the method returns “/socialloginsuccess.” That’s the URL path for a successful login.

The getAuthentication() method constructs an Authentication object. In this case, it’s an instance of UserNamePasswordAuthenticationToken.

First, the code gets a List of roles for the user. Here, that List only contains one item. Feel free to expand that list based on your own requirements, though.

The next line of code sets the password to null. A password isn’t needed here because the user logged in via social media.

Finally, the code instantiates the Authentication object with the user’s ID, null password, and List of roles.

The last method in the code block above determines how the user logged in (with Facebook or Twitter) and then sets the role accordingly.

 

The ConnectionSignUp Class

When a user logs in via Facebook or Twitter, you’re almost certainly going to want to do something with that user information. You’ll probably want to persist it.

It’s beyond the scope of this tutorial to get into persistence, but you’ll see enough here to at least get the ball rolling.

Take a look at the SocialConnectionSignup class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class SocialConnectionSignup implements ConnectionSignUp {
 
    @Autowired
    UserHelper userHelper;