Develop a Facebook-App with Spring-Social – Part III: Implementing a UserIdSource

In this series of Mini-How-Tow’s I will describe how to develop a facebook app with the help of Spring-Social

In the last part of this series, I explained, why the nice little example from the Getting-Started-Guide “Accessing Facebook Data” cannot function as a real facebook-app.

In this part, we will try to solve that problem, by implementing a UserIdSource, that tells Spring Social, which user it should connect to the API.

The Source is With You

You can find the source-code on http://juplo.de/git/examples/facebook-app/ and browse it via gitweb. Check out part-03 to get the source for this part of the series.

Introducing UserIdSource

The UserIdSource is used by Spring Social to ask us, which user it should connect with the social net. Clearly, to answer that question, we must remeber, which user we are currently interested in!

Remember Your Visitors

In order to remember the current user, we implement a simple mechanism, that stores the ID of the current user in a cookie and retrieves it from there for subsequent calls. This concept was borrowed — again — from the official code examples. You can find it for example in the quickstart-example.

It is crucial to stress, that this concept is inherently insecure and should never be used in a production-environment. As the ID of the user is stored in a cookie, an attacker could simply take over control by sending the ID of any currently connected user, he is interested in.

The concept is implemented here only for educational purposes. It will be replaced by Spring Security later on. But for the beginning, it is easier to understand, how Spring Social works, if we implement a simple version of the mechanism ourself.

Pluging in Our New Memory

The internals of our implementation are not of interest. You may explore them by yourself. In short, it stores the ID of each new user in a cookie. By inspecting that cookie, it can restore the ID of the user on subsequent calls.

What is from interest here is, how we can plug in this simple example-mechanism in Spring Social.

Mainly, there are two hooks to do that, that means: two interfaces, we have to implement:

  1. UserIdSource: Spring Social uses an instance of this interface to ask us, which users authorizations it should load from its persistent store of user/connection-mappings. We already have seen an implementation of that one in the last part of our series.
  2. ConnectionSignUp: Spring Social uses an instance of this interface, to ask us about the name it should use for a new user during sign-up.

Implementation

The implementation of ConnectionSignUp simply uses the ID, that is provided by the social network. Since we are only signing in users from Facebook, these ID’s are guaranteed to be unique.

public class ProviderUserIdConnectionSignUp implements ConnectionSignUp
{
  @Override
  public String execute(Connection connection)
  {
    return connection.getKey().getProviderUserId();
  }
}

The implementation of UserIdSource retrieves the ID, that was stored in the SecurityContext (our simple implementation — not to be confused with the class from Spring Security). If no user is stored in the SecurityContext, it falls back to the old behavior and returns the fix id anonymous.

public class SecurityContextUserIdSource implements UserIdSource
{
  private final static Logger LOG =
      LoggerFactory.getLogger(SecurityContextUserIdSource.class);


  @Override
  public String getUserId()
  {
    String user = SecurityContext.getCurrentUser();
    if (user != null)
    {
      LOG.debug("found user \"{}\" in the security-context", user);
    }
    else
    {
      LOG.info("found no user in the security-context, using \"anonymous\"");
      user = "anonymous";
    }
    return user;
  }
}

Actual Plumbing

To replace the AnonymousUserIdSource by our new implementation, we simply instantiate that instead of the old one in our configuration-class SocialConfig:

@Override
public UserIdSource getUserIdSource()
{
  return new SecurityContextUserIdSource();
}

There are several ways to plug in the ConnectionSignUp. I decided, to plug it into the instance of InMemoryUsersConnectionRepository, that our configuration uses, because this way, the user will be signed up automatically on sign in, if it is not known to the application:

@Override
public UsersConnectionRepository getUsersConnectionRepository(
    ConnectionFactoryLocator connectionFactoryLocator
    )
{
  InMemoryUsersConnectionRepository repository =
      new InMemoryUsersConnectionRepository(connectionFactoryLocator);
  repository.setConnectionSignUp(new ProviderUserIdConnectionSignUp());
  return repository;
}

This makes sense, because our facebook-app uses Facebook, to sign in its users, and, because of that, does not have its own user-model. It can just reuse the user-data provided by facebook.

The other approach would be, to officially sign up users, that are not known to the app. This is achieved, by redirecting to a special URL, if a sign-in fails, because the user is unknown. These URL then presents a formular for sign-up, which can be prepopulated with the user-data provided by the social network. You can read more about this approach in the official documentation.

Run It!

So, let us see, if our refinement works. Run the following command and log into your app with at least two different users:

git clone http://juplo.de/git/examples/facebook-app/
cd facebook-app
checkout part-00
mvn spring-boot:run \
    -Dfacebook.app.id=YOUR_ID \
    -Dfacebook.app.secret=YOUR_SECRET \
    -Dlogging.level.de.juplo.yourshouter=debug

(The last part of the command turns on the DEBUG logging-level, to see in detail, what is going on.

But What The *#! Is Going On There?!?

Unfortunately, our application shows exactly the same behavior as, before our last refinement. Why that?

If you run the application in a debugger and put a breakpoint in our implementation of ConnectionSignUp, you will see, that this code is never called. But it is plugged in in the right place and should be called, if a new user signs in!

The solution is, that we are using the wrong mechanism. We are still using the ConnectController which was configured in the simple example, we extended. But this controller is meant to connect a known user to one or more new social services. This controller assumes, that the user is already signed in to the application and can be retrieved via the configured UserIdSource.

To sign in a user to our application, we have to use the ProviderSignInController instead!

Coming next…

In the next part of this series, I will show you, how to change the configuration, so that the ProviderSignInController is used to sign in (and automatically sign up) users, that were authenticated through the Graph-API from Facebook.

Funded by the Europian Union

This article was published in the course of a resarch-project, that is funded by the European Union and the federal state Northrhine-Wetphalia.

Europäische Union: Investitionen in unsere Zukunft - Europäischer Fonds für regionale Entwicklung EFRE.NRW 2014-2020: Invesitionen in Wachstum und Beschäftigung

Leave a Reply

Your email address will not be published. Required fields are marked *