Announcement Announcement Module
Collapse
No announcement yet.
Facebook Provider Sign-in Problem. Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Facebook Provider Sign-in Problem.

    Good day ,

    I am having a small problem with my web app . I am trying to use spring social to provide a Facebook login feature for my app. I have a database with my local users accounts (PostgreSQL) and I'm using hibernate .

    So far this is what i have accomplished. When a user comes to the sign in page , he/she can login using their username and password or click a <Sign-in with Facebook> button. When they click the button it posts to /signin/facebook and the ProviderSignInController kicks in and connects to Facebook , does the authentication and then sends the user to the signup page where it pre-fills some the fields (First Name , Last Name , email address) from the Facebook profile. they can then fill out the remaining fields and submit , where it saved to my database. I have a field in my database account table that stores the Facebook userid.

    My problem is now , even after the account is created , when i click the <sign-in with Facebook> button , after it does the authorization by Facebook and redirects , it still doesn't make the link to the local user account , and prompts the user to sign up again .

    I know where my problem lies , its a matter of how to fix it . I am trying to figure out how exactly the providersignincontroller tries to match the Facebook account to my local users account.

    Any help will be greatly appreciated.

    Thanks.

  • #2
    Based on what you said first I would check UserConnection table which maps your local userid to the providerUserid. If that is looks good I would then check SignInAdapter which you provide to ProviderSignInController. ProviderSignInController will call your SignInAdapter's signIn method where you have to do the user login yourself by populating the securityContext.

    This might help GitHub - Spring Social Showcase and Social Config
    Last edited by binnyg; Apr 7th, 2012, 04:07 PM.

    Comment


    • #3
      You say you have an account table that includes the Facebook user ID...that's fine if it helps your application code somehow, but that's not enough for Spring Social. Spring Social wants to manage connections in a UserConnection table. This table essentially joins the application-specific user ID with a provider (e.g., Facebook, Twitter) account as well as a few other bits of info about the connection.

      After your code saves the new account info to the database, do you follow that up with a call to ProviderSignInUtils.handlePostSignUp()? If not, then the connection is never created in the UserConnection table.

      For example, the Spring Social Showcase example has the following code that handles the signup form submission:

      Code:
      	@RequestMapping(value="/signup", method=RequestMethod.POST)
      	public String signup(@Valid SignupForm form, BindingResult formBinding, WebRequest request) {
      		if (formBinding.hasErrors()) {
      			return null;
      		}
      		Account account = createAccount(form, formBinding);
      		if (account != null) {
      			SignInUtils.signin(account.getUsername());
      			ProviderSignInUtils.handlePostSignUp(account.getUsername(), request);
      			return "redirect:/";
      		}
      		return null;
      	}
      Notice near the end, after the account has been created successfully (and is not null), the code uses SignInUtils to automatically sign the user into the application and then uses ProviderSignInUtils.handlePostSignUp() to complete the connection between the newly created user and the provider (e.g., Facebook).

      Comment


      • #4
        persistent storage of userConnections

        hi , i solved the problem using the information you provided. Thank you very much . I understand where the users connection comes into play now. however one thing though . if i restart my server the user connections are lost. how can i save these connections to a table in my db ?

        Comment


        • #5
          With Spring Social Showcase, it's using an in-memory database, which is great for demos and development, but horrible for production for the reasons you've just given. I'm assuming that the reason your connections are lost is because you're using the embedded database. Rather than use the embedded database, you should use a datasource that's tied to a real DB.

          Comment


          • #6
            Thank you Craig , I got it working by creating the userconnection table in my db and pointing the datasource to it. Everything works fine now. The only thing i noticed when i created the table is that i had to change refreshtoken and secret fields from not null to allow nulls because this was throwing an error.

            Thanks again for your help.

            Comment


            • #7
              Hmmm...those columns are already defined to allow nulls, per JdbcUsersConnectionRepository.sql:

              Code:
              create table UserConnection (userId varchar(255) not null,
              	providerId varchar(255) not null,
              	providerUserId varchar(255),
              	rank int not null,
              	displayName varchar(255),
              	profileUrl varchar(512),
              	imageUrl varchar(512),
              	accessToken varchar(255) not null,					
              	secret varchar(255),
              	refreshToken varchar(255),
              	expireTime bigint,
              	primary key (userId, providerId, providerUserId));
              create unique index UserConnectionRank on UserConnection(userId, providerId, rank);

              Comment


              • #8
                ok thanks. i have another question , i have the provider sign-in working perfectly now , i now want to add some additional Facebook functionality to my app , however if i use connect/facebook it creates an additional connection in my user connections table which i do not want. i know i can simply extract the access token from the table and use it , but i want to use spring social functions .

                Comment


                • #9
                  I'm not clear on what you're saying...are you saying that you use Provider Sign-In for authenticating into your app (which creates a connection if one didn't already exist) and then go through the ConnectController flow again (which would create another connection)? If so, that's not how it was intended to be used.

                  The idea is that if the user has previously created a connection with ConnectController, then they can sign in to the application using ProviderSignInController and not have to enter their app credentials...the previously-established connection will serve as authentication. But, if they haven't created a connection before, then ProviderSignInController can let them sign in via Facebook (or some other provider) and create a new account that is already connected to the provider. There would be no need to go through the connection flow again, because the account would already be connected to the provider (Facebook, in your case).

                  If that's not what you're saying, then please clarify.

                  Comment


                  • #10
                    I'm sorry if i didn't make myself clear. what I really wanted to find out is how to get extract a Facebook connection from the users connection repository so i can create a Facebook template object and perform Facebook operations. I have already realized that i don't need to to use connectController. at present what i do is manually retrieve the accesstoken from my userconnection table and then create the Facebook template object from that.

                    Comment


                    • #11
                      Ah, I see. Then I certainly can help.

                      One of the main benefits of Spring Social is that (unless you want to), you don't ever have to deal with OAuth yourself. You configure the connection factory with the app's key and secret and then you're done dealing with OAuth. You can then ask the connection repository for a connection and from that get the API binding.

                      Have a look at the Spring Social Showcase sample, specifically at SocialConfig.java (https://github.com/SpringSource/spri...ialConfig.java). This shows how the connection repository can give you a Facebook API binding...here's a snippet from that file:

                      Code:
                      @Bean
                      @Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
                      public Facebook facebook() {
                      Connection<Facebook> facebook = connectionRepository().findPrimaryConnection(Facebook.class);
                      return facebook != null ? facebook.getApi() : new FacebookTemplate();
                      }

                      Comment


                      • #12
                        here lies my problem. when i try to do this . my facebook object is null. its not finding the connection in the connectionRepository. remember i had previously modified my application to user a local userconnections table. i'm not sure if this is what is causing the problem.

                        Comment


                        • #13
                          That doesn't make much sense to me: You say that the connections are being written to the UserConnection table, but the connection repository can't find them? If it's the same connection repository wired with the same data source, then I don't see how the connection repository wouldn't be able to find that table. The connection repository was responsible for storing those connections in the first place, so why couldn't it find them when you later ask for them?

                          Would you mind posting the pertinent configuration here? At this point I'm just speculating (and not even able to do that effectively)...perhaps seeing how you've configured these beans would make a difference.

                          Comment


                          • #14
                            this is my socialConfig class :

                            Code:
                            /*
                             * Copyright 2011 the original author or authors.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             *      http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            package com.teenjobs.web.controllers.social.config;
                            
                            import javax.inject.Inject;
                            import javax.sql.DataSource;
                            
                            import org.springframework.context.annotation.Bean;
                            import org.springframework.context.annotation.Configuration;
                            import org.springframework.context.annotation.Scope;
                            import org.springframework.context.annotation.ScopedProxyMode;
                            import org.springframework.core.env.Environment;
                            import org.springframework.security.core.Authentication;
                            import org.springframework.security.core.context.SecurityContextHolder;
                            import org.springframework.security.crypto.encrypt.Encryptors;
                            import org.springframework.security.web.savedrequest.RequestCache;
                            import org.springframework.social.connect.Connection;
                            import org.springframework.social.connect.ConnectionFactoryLocator;
                            import org.springframework.social.connect.ConnectionRepository;
                            import org.springframework.social.connect.UsersConnectionRepository;
                            import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
                            import org.springframework.social.connect.support.ConnectionFactoryRegistry;
                            import org.springframework.social.connect.web.ConnectController;
                            import org.springframework.social.connect.web.ProviderSignInController;
                            import org.springframework.social.facebook.api.Facebook;
                            import org.springframework.social.facebook.api.impl.FacebookTemplate;
                            import org.springframework.social.facebook.connect.FacebookConnectionFactory;
                            import org.springframework.social.linkedin.api.LinkedIn;
                            import org.springframework.social.linkedin.connect.LinkedInConnectionFactory;
                            
                            import com.teenjobs.persistence.repository.AccountRepository;
                            import com.teenjobs.web.social.facebook.PostToWallAfterConnectInterceptor;
                            import com.teenjobs.web.social.signin.SimpleSignInAdapter;
                            import com.teenjobs.web.social.twitter.TweetAfterConnectInterceptor;
                            import org.springframework.social.twitter.api.Twitter;
                            import org.springframework.social.twitter.api.impl.TwitterTemplate;
                            import org.springframework.social.twitter.connect.TwitterConnectionFactory;
                            
                            /**
                             * Spring Social Configuration.
                             * @author Craig Walls
                             */
                            @Configuration
                            public class SocialConfig {
                            
                            	@Inject
                            	private Environment environment;
                            
                            	@Inject
                            	private DataSource dataSource;
                            	
                            	@Inject
                            	private AccountRepository accountrepository;
                            
                            	@Bean
                            	@Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES) 
                            	public ConnectionFactoryLocator connectionFactoryLocator() {
                            		ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
                            		registry.addConnectionFactory(new TwitterConnectionFactory(environment.getProperty("twitter.consumerKey"),
                            				environment.getProperty("twitter.consumerSecret")));
                            		registry.addConnectionFactory(new FacebookConnectionFactory(environment.getProperty("facebook.clientId"),
                            				environment.getProperty("facebook.clientSecret")));
                            		registry.addConnectionFactory(new LinkedInConnectionFactory(environment.getProperty("linkedin.consumerKey"),
                            				environment.getProperty("linkedin.consumerSecret")));
                            		return registry;
                            	}
                            
                            	@Bean
                            	@Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES) 
                            	public UsersConnectionRepository usersConnectionRepository() {
                            		return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator(), Encryptors.noOpText());
                            	}
                            
                            	@Bean
                            	@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)	
                            	public ConnectionRepository connectionRepository() {
                            		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                            		if (authentication == null) {
                            			throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
                            		}
                            		return usersConnectionRepository().createConnectionRepository(authentication.getName());
                            	}
                            
                            	@Bean
                            	@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)	
                            	public Facebook facebook() {
                            		Connection<Facebook> facebook = connectionRepository().findPrimaryConnection(Facebook.class);
                            		return facebook != null ? facebook.getApi() : new FacebookTemplate();
                            	}
                            	
                            	@Bean
                            	@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)	
                            	public Twitter twitter() {
                            		Connection<Twitter> twitter = connectionRepository().findPrimaryConnection(Twitter.class);
                            		return twitter != null ? twitter.getApi() : new TwitterTemplate();
                            	}
                            
                            	@Bean
                            	@Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)	
                            	public LinkedIn linkedin() {
                            		Connection<LinkedIn> linkedin = connectionRepository().findPrimaryConnection(LinkedIn.class);
                            		return linkedin != null ? linkedin.getApi() : null;
                            	}
                            
                            	@Bean
                            	public ConnectController connectController() {
                            		ConnectController connectController = new ConnectController(connectionFactoryLocator(), connectionRepository());
                            		connectController.addInterceptor(new PostToWallAfterConnectInterceptor());
                            		connectController.addInterceptor(new TweetAfterConnectInterceptor());
                            		return connectController;
                            	}
                            
                            	@Bean
                            	public ProviderSignInController providerSignInController(RequestCache requestCache) {
                            		ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator(), usersConnectionRepository(), new SimpleSignInAdapter(requestCache,accountrepository));
                            	    providerSignInController.setPostSignInUrl("/"); 
                            		providerSignInController.setSignUpUrl("/signup/teens");
                            		return providerSignInController;
                            	}
                            
                            }

                            and my mainconfig for the spring social.

                            Code:
                            /*
                             * Copyright 2011 the original author or authors.
                             *
                             * Licensed under the Apache License, Version 2.0 (the "License");
                             * you may not use this file except in compliance with the License.
                             * You may obtain a copy of the License at
                             *
                             *      http://www.apache.org/licenses/LICENSE-2.0
                             *
                             * Unless required by applicable law or agreed to in writing, software
                             * distributed under the License is distributed on an "AS IS" BASIS,
                             * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                             * See the License for the specific language governing permissions and
                             * limitations under the License.
                             */
                            package com.teenjobs.web.controllers.social.config;
                            
                            import javax.sql.DataSource;
                            
                            
                            
                            import org.springframework.beans.factory.annotation.Autowired;
                            import org.springframework.context.annotation.ComponentScan;
                            import org.springframework.context.annotation.ComponentScan.Filter;
                            import org.springframework.context.annotation.Configuration;
                            import org.springframework.context.annotation.PropertySource;
                            import org.springframework.jdbc.core.JdbcTemplate;
                            
                            import org.springframework.jdbc.datasource.DataSourceTransactionManager;
                            
                            import org.springframework.transaction.annotation.EnableTransactionManagement;
                            
                            /**
                             * Main configuration class for the application.
                             * Turns on @Component scanning, loads externalized application.properties, and sets up the database.
                             * @author Craig Walls
                             */
                            @Configuration
                            @ComponentScan(basePackages = "com.teenjobs.web", excludeFilters = { @Filter(Configuration.class) })
                            @PropertySource("classpath:com/teenjobs/web/controllers/social/config/application.properties")
                            @EnableTransactionManagement
                            public class MainConfig {
                            
                            	 private JdbcTemplate jdbcTemplate;
                            	private DataSourceTransactionManager transactionManager;
                            	 
                            
                            	    @Autowired
                            	    public void setDataSource(DataSource dataSource) {
                            	        this.jdbcTemplate = new JdbcTemplate(dataSource);
                            	        this.transactionManager = new DataSourceTransactionManager(dataSource);
                            	    }
                            	
                            	
                            
                            }
                            basically all i did was set the dataSource to my db. which is configured in an xml file.
                            Last edited by habuma; Apr 24th, 2012, 08:35 AM.

                            Comment


                            • #15
                              I must admit that I'm a bit stumped. I'm sure there's some key info that I'm either overlooking or isn't presented here. I've stared at this code you've shared for quite awhile and see nothing obvious that's out of place.

                              But it seems to me that if in the course of a provider sign-in a connection is, in fact, created in the database, then that connection should be found later when findByPrimaryConnection() is called.

                              That assumes, of course, that the authenticated user is the same authenticated user for which the connection was created. Ultimately, the query performed is as follows:

                              Code:
                              select userId, providerId, providerUserId, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime from UserConnection where userId = ? and providerId = ? and rank = 1
                              Where the userId is the same as whatever value is passed into createConnectionRepository() and providerId is whatever the provider connection factory says it is for the given API interface (in the case of Facebook, it will be "facebook").

                              Can we be sure that the connection is created for the very same user for whom the authentication is retrieved when creating the connection repository? More specifically, can we confirm that the userId field in the UserConnection table holds the same value as the value returned from authentication.getName()?

                              To help with that, you might want to do a bit of debugging in the connectionRepository() method of your SocialConfig.java and then compare what is passed into createConnectionRepository() with what is in the userId field in the UserConnection table.

                              That's the only theory I have right now. Once your eliminate or confirm that as the issue, I can keep looking.

                              Comment

                              Working...
                              X