Get the Code: https://github.com/iainporter/rest-java
This post will cover OAuth Authentication with Facebook. Most REST applications provide a user with a choice of signing up with their email address and password or to delegate to a third-party security provider such as Facebook, Twitter, Google, etc. The solution in the rest-java application uses Javascript libraries on the client side for the oauth dance and Spring Social on the server for communicating with the Facebook social graph API.
Client Code
Whether the client is a web page or a mobile device the desired end result is the same: to get an access token from the Facebook API.
The client code in scr/main/webapp/index.html:
When registering the app with Facebook we insert the appId in line 03.window.fbAsyncInit = function() { FB.init({ appId : '133718006790561', // App ID status : true, // check login status cookie : true, // enable cookies to allow the server to access the session xfbml : true // parse XFBML }); $('.fb').on('click', function () { FB.login(function(response) { console.log(response) if (response.authResponse) { javaRest.user.loginSocial(response.authResponse.accessToken, function (error) { if (error) console.log(error) else window.location = 'dashboard.html' }) console.log('Welcome! Fetching your information.... '); FB.api('/me', function(response) { console.log('Good to see you, ' + response.name + '.'); }); } else { console.log('User cancelled login or did not fully authorize.'); } }); }) }; // Load the SDK Asynchronously (function(d){ var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0]; if (d.getElementById(id)) {return;} js = d.createElement('script'); js.id = id; js.async = true; js.src = "//connect.facebook.net/en_US/all.js"; ref.parentNode.insertBefore(js, ref); }(document));
Facebook makes a callback with an authorization popup window.
The user accepts and we receive back an access token which we then use to call the API (line 14).
Server API Endpoint
Once the client has an access token they then call the server API which has an endpoint of:
user/login/<providerId>
In this case the providerId is facebook
The json payload is simply {"accessToken":"<the User's Facebook access token>"}
On the server side we want to achieve three things:
1. Access the user's Facebook profile and persist some relevant details
2. Match the profile up with an existing user account or else create a new one
3. Return an Authenticated User Token for API access so that we can treat Facebook logins the same as an email login
The implementation method for social login is:
The JpaUsersConnectionRepository is used to connect to Facebook and retrieve the User's profile. It then matches it up to an existing user or else creates a new one if this is the first login attempt.
Once we have an API User we then create or update a SocialUser profile and associate it with the User.
The relevant method:
Step 1. Create an application on Facebook and if you want to test it locally set the site url to http://localhost:8080/java-rest
Step 2. Insert your App Id and App Secret in src/main/resources/app.properties
Step 3. Insert your appId in to the relevant place in the javascript in src/main/webapp/index.html and src/main/webapp/signup.html
Step 4. Start the application by running :
Step 5. In a browser window request http://localhost:8080/java-rest/index.html
You should be able to login with your Facebook credentials
This can easily be extended to include other OAuth providers such as Twitter or Google
That wraps up this post.
In the next post I will cover the Lost Password API.
User Resource
@PermitAll @Path("login/{providerId}") @POST public Response socialLogin(@PathParam("providerId") String providerId, OAuth2Request request) { OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory<?>) connectionFactoryLocator.getConnectionFactory(providerId); Connection<?> connection = connectionFactory.createConnection(new AccessGrant(request.getAccessToken())); AuthenticatedUserToken token = userService.socialLogin(connection); return getLoginResponse(token); }
We get a ConnectionFactory using the ConnectionFactoryLocator that was configured in SocialConfig.java
The factory class then creates a connection using the access token and the service method is called to login the user.
A User has a many-to-one relationship with SocialUser instances. In the SocialUser model we need to capture several important pieces of information such as:
The factory class then creates a connection using the access token and the service method is called to login the user.
SocialConfig.java
@Configuration public class SocialConfig { @Autowired ApplicationConfig config; @Autowired SocialUserRepository socialUserRepository; @Autowired UserRepository userRepository; @Autowired TextEncryptor textEncryptor; @Bean public ConnectionFactoryLocator connectionFactoryLocator() { ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry(); registry.addConnectionFactory(new FacebookConnectionFactory( config.getFacebookClientId(), config.getFacebookClientSecret())); return registry; } @Bean public UsersConnectionRepository usersConnectionRepository() { JpaUsersConnectionRepository usersConnectionRepository = new JpaUsersConnectionRepository(socialUserRepository, userRepository, connectionFactoryLocator(), textEncryptor); return usersConnectionRepository; } }
Social User Domain Model
A User has a many-to-one relationship with SocialUser instances. In the SocialUser model we need to capture several important pieces of information such as:
- providerId - the security implementation provider (facebook)
- providerUserId - the identifier within the Facebook universe
- accessToken - the key needed to access the user's profile in Facebook (encrypted in the DB)
We can also capture other useful information such as profileImage, profileUrl, displayName, etc.
Note that we cannot guarantee to get an email address.
User Service
The implementation method for social login is:
@Transactional public AuthenticatedUserToken socialLogin(Connection<?> connection) { List<String> userUuids = jpaUsersConnectionRepository.findUserIdsWithConnection(connection); if(userUuids.size() == 0) { throw new AuthenticationException(); } User user = userRepository.findByUuid(userUuids.get(0)); //take the first one if there are multiple userIds for this provider Connection if (user == null) { throw new AuthenticationException(); } updateUserFromProfile(connection, user); return new AuthenticatedUserToken(user.getUuid().toString(), createAuthorizationToken(user).getToken()); }
The JpaUsersConnectionRepository is used to connect to Facebook and retrieve the User's profile. It then matches it up to an existing user or else creates a new one if this is the first login attempt.
Once we have an API User we then create or update a SocialUser profile and associate it with the User.
JpaUsersConnectionRepository
The relevant method:
/** * Find User with the Connection profile (providerId and providerUserId) * If this is the first connection attempt there will be nor User so create one and * persist the Connection information * In reality there will only be one User associated with the Connection * * @param connection * @return List of User Ids (see User.getUuid()) */ public List<String> findUserIdsWithConnection(Connection<?> connection) { List<String> userIds = new ArrayList<String>(); ConnectionKey key = connection.getKey(); List<SocialUser> users = socialUserRepository.findByProviderIdAndProviderUserId (key.getProviderId(), key.getProviderUserId()); if (!users.isEmpty()) { for (SocialUser user : users) { userIds.add(user.getUser().getUuid().toString()); } return userIds; } //First time connected so create a User account or find one that is already //created with the email address User user = findUserFromSocialProfile(connection); String userId; if(user == null) { userId = userService.createUser(Role.authenticated).getId(); } else { userId = user.getUuid().toString(); } //persist the Connection createConnectionRepository(userId).addConnection(connection); userIds.add(userId); return userIds; } private User findUserFromSocialProfile(Connection connection) { User user = null; UserProfile profile = connection.fetchUserProfile(); if(profile != null && StringUtils.hasText(profile.getEmail())) { user = userRepository.findByEmailAddress(profile.getEmail()); } return user; }
Testing the API
Step 1. Create an application on Facebook and if you want to test it locally set the site url to http://localhost:8080/java-rest
Step 2. Insert your App Id and App Secret in src/main/resources/app.properties
Step 3. Insert your appId in to the relevant place in the javascript in src/main/webapp/index.html and src/main/webapp/signup.html
Step 4. Start the application by running :
gradle tomcatRun
Step 5. In a browser window request http://localhost:8080/java-rest/index.html
You should be able to login with your Facebook credentials
This can easily be extended to include other OAuth providers such as Twitter or Google
That wraps up this post.
In the next post I will cover the Lost Password API.
similarly, I had blogged about openid authentication with google. Some readers may find it useful.
ReplyDeleteCan you provide client steps (using javascript) to pull the accessToken code for twitter?
ReplyDeleteNice writeup.
ReplyDeleteThank for the excellent explanation. You got me exactly what I'm looking for. Just wondering if you have maven build for this.
ReplyDeleteHi Could you please suggest to me how to write the Restful service in java to generate the TOKEN
ReplyDeletecould you provide what and all steps required for google+ login..
ReplyDeleteIF you are looking for a solution like an extension of Spring Social Security, Check this out :
ReplyDeletehttp://stackoverflow.com/questions/35911723