Workaround for broken Facebook OAuth using Spring Social

· 4 min read
Post featured image

Here at LingoHub we make heavy use of the Spring technology stack in our backend, in particular we use Spring Social to implement the OAuth workflow which enables you to login at LingoHub using your GitHub, Facebook, ... accounts. Just yesterday we realized that authentication via Facebook stopped working so we were looking for the reason and a quick workaround. There seems to be an issue with the current release version of the Spring Social plugin that leads to a deserialization error when retrieving the details for a specific user as pointed out in this bug report.

Since we did not want to wait for an official bugfix release to be available we decided to implement our own workaround which seems to work well for now.

What exactly is the problem?

Spring Social uses Jackson to deserialize the JSON data returned by the Facebook Graph API. As pointed out in the bug report the class 'VideoUploadLimits' uses an incompatible data type which causes the retrieval of the user details to fail. So as a workaround we decided to implement our own User object for now which simply does not deserialize the problematic response information at all as we do not require that information anyways:

 @SuppressWarnings("serial")
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class LingohubUser extends FacebookObject implements Serializable {
   ...
   private VideoUploadLimits videoUploadLimits;
   ...
   public VideoUploadLimits getVideoUploadLimits() {
     return videoUploadLimits;
   }
 }

In order to make use of that alternative implementation we had to exchange some other components as well, most notably the FacebookAdapter class which is responsible for retrieving the user details during the OAuth workflow:

 public class LingohubFacebookAdapter extends FacebookAdapter {

   static final String[] FIELDS = {
     ..., 
     "verified", 
     "video_upload_limits", 
     "viewer_can_send_gift", 
     "website", 
     "work"
   };

   public boolean test(Facebook facebook) {
     try {
       facebook.fetchObject("me", LingohubUser.class, FIELDS);
       return true;
     } catch (ApiException e) {
       return false;
     }
   }

   public void setConnectionValues(Facebook fb, ConnectionValues values) {
     LingohubUser profile = fb.fetchObject("me", LingohubUser.class, FIELDS);
     values.setProviderUserId(profile.getId());
     values.setDisplayName(profile.getName());
     values.setProfileUrl(profile.getLink());
     values.setImageUrl(GraphApi.GRAPH_API_URL + profile.getId() + "/picture");
   }

   public UserProfile fetchUserProfile(Facebook fb) {
     LingohubUser profile = fb.fetchObject("me", LingohubUser.class, FIELDS);
     return new UserProfileBuilder().setName(profile.getName())
       .setFirstName(profile.getFirstName())
       .setLastName(profile.getLastName())
       .setEmail(profile.getEmail()).build();
   }
 }

Finally to make use of this alternative Adapter implementation we implemented our own version of an OAuth2ConnectionFactory as shown below:

 public class LingohubFacebookConnectionFactory 
   extends OAuth2ConnectionFactory<Facebook> {

   public LingohubFacebookConnectionFactory(String appId, String appSecret) {
     this(appId, appSecret, null);
   }

   public LingohubFacebookConnectionFactory(String appId, String appSecret, String appNS) {
     super("facebook", new FacebookServiceProvider(appId, appSecret, appNS), 
       new LingohubFacebookAdapter());
   }
 }

and to make actual use of that Factory we had to adapt our version of the SocialConfigurerAdapter, for which we already used a custom implementation as shown below:

 @Configuration
 @EnableSocial
 public class LingohubSocialConfigurerAdapter extends SocialConfigurerAdapter {
   ...
   @Override
   public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
     cfConfig.addConnectionFactory(
       new TwitterConnectionFactory("twitter.clientId", "twitter.clientSecret"));
     cfConfig.addConnectionFactory(
       new LingohubFacebookConnectionFactory("facebook.clientId", "facebook.clientSecret"));
     ...
   }
   ...
 }

Voilà, these were all the changes necessary to fix the authentication issues and you are now able to login with your Facebook account again. Of course this is intended only as temporary workaround until a new version of the Spring Social API is available.

Clock calendar notifications in harmony

Start your 14-day free trial