Inspired by the older Facebook-Connect-Example I wrote a Login-panel for the new Facebook Javascript SDK.
For the REST-calls to the new Graph-API I use RestFB
I used mostly plain JavaScript, but you can put it easily into a wrapper for your convenience.
<html xmlns:wicket> <body> <wicket:head> <script type="text/javascript" wicket:id="loginCallback"></script> <script type="text/javascript" wicket:id="logoutCallback"></script> </wicket:head> <wicket:panel> <fb:login-button autologoutlink="true" perms="email" > </fb:login-button> <!-- Facebook-API --> <div id="fb-root"> </div> <script wicket:id="FBapi"> <!-- calling Facebook API, init and check login --> </script> </wicket:panel> </body> </html>
The panel is as minimalistic as possible. You can simply put it on the header of your design and use it as login-indicator and -button or extend the code to a more sophisticated login-page.
package x.y.z.authentication; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.wicket.Page; import org.apache.wicket.RequestCycle; import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.JavascriptPackageResource; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.protocol.http.WebRequestCycle; import x.y.z.AuthWebSession; /** * Minimalistic Panel for Facebook-Login-Management * * - as a single Button you can put into every Page * - as a basis for a full-blown Login-Page * * @author Markus Lachinger <markus.lachinger äT gmail com> * */ public class FacebookSignInPanel extends Panel { /** * default SID */ private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(FacebookSignInPanel.class); protected String apiId = "yourAPIkey"; protected String secret = "yourSecret"; /** Wrapper to super() **/ public FacebookSignInPanel(String id) { super(id); createPanel(); } /** * This method will load the Facebook-API and manage login-states */ public void createPanel() { // Add and initialize Facebook-API add(JavascriptPackageResource .getHeaderContribution("http://connect.facebook.net/en_US/all.js")); StringBuffer sb = new StringBuffer(); sb.append("FB.init({"); sb.append(" appId: '"+ apiId +"',"); sb.append(" status: true,"); sb.append(" cookie: true,"); sb.append(" xfbml: true"); sb.append("});"); // set Event-Handler for Login and Logout sb.append("FB.Event.subscribe('auth.login', function(respond){callWicketLogin(respond)});"); sb.append("FB.Event.subscribe('auth.logout', function(respond){callWicketLogout(respond)});"); // If the User is already logged in into Facebook, send this to Session if(!((AuthWebSession) getSession()).isSignedIn()) { sb.append("FB.getLoginStatus(function(response){"); sb.append("if (response.session) {"); sb.append(" FB.api('/me', function(response){"); sb.append(" if(response.session) {"); sb.append(" callWicketLogin(response);"); sb.append(" } else {"); sb.append(" FB.login(function(res) {"); sb.append(" callWicketLogin(res); }"); sb.append(" )"); sb.append(" }"); sb.append(" })"); sb.append(" };"); // sb.append(" else {"); // sb.append(" alert(\"not logged in\");"); // sb.append(" }"); sb.append(" });"); } Label FBapi = new Label("FBapi", sb.toString()); FBapi.setEscapeModelStrings(false); add(FBapi); /** * Javascript-Wicket-Bridge that gets called after a successful login * contains Facebook-UserID, Facebook-SessionId for i.e. JSON and * AccessToken (OAuth2) */ final AbstractDefaultAjaxBehavior loginBehavior = new AbstractDefaultAjaxBehavior() { protected void respond(final AjaxRequestTarget target) { handleLoginEventCallback(target.getPage()); } }; add(loginBehavior); /** * Javascript-Wicket-Bridge that gets called after a logout occurred */ final AbstractDefaultAjaxBehavior logoutBehavior = new AbstractDefaultAjaxBehavior() { protected void respond(final AjaxRequestTarget target) { //log out the user but keep the session ((AuthWebSession) getSession()).signOut(); } }; add(logoutBehavior); Label loginCallback = new Label("loginCallback", new AbstractReadOnlyModel<String>() { @Override public String getObject() { CharSequence url = loginBehavior.getCallbackUrl(); StringBuffer sb = new StringBuffer(); sb.append("function callWicketLogin(response) { \n"); sb.append(" if (response.session) {\n"); //optional: check permissions here if all necessary are granted! //example: (2010/6/1 @ http://developers.facebook.com/docs/reference/javascript/FB.login) // if (response.perms) { // // user is logged in and granted some permissions. // // perms is a comma separated list of granted permissions // } else { // // user is logged in, but did not grant any permissions // } sb.append(" var wcall = wicketAjaxGet('"); sb.append(url); sb.append("&fbid='+ response.session.uid +'"); //SessionId in case you want to use the old API sb.append("&fbsessid='+ response.session.session_key +'"); //AccessToken for new OAuth2-based requests sb.append("&fbaccesstoken='+ response.session.access_token"); sb.append(", function() { }, function() { });\n"); sb.append(" } else {\n"); sb.append(" alert(\"Sorry, there was some kind of error in the Facebook-login. Please try again.\");"); sb.append(" }\n"); sb.append("}\n"); return sb.toString(); } }); loginCallback.setEscapeModelStrings(false); loginCallback.setOutputMarkupId(true); add(loginCallback); Label logoutCallback = new Label("logoutCallback", new AbstractReadOnlyModel<String>() { @Override public String getObject() { CharSequence url = logoutBehavior.getCallbackUrl(); StringBuffer sb = new StringBuffer(); sb.append("function callWicketLogout(response) { \n"); sb.append("alert(\"You have been logged out from facebook. We will log you out too - see FAQ\");"); sb.append(" var wcall = wicketAjaxGet('"); sb.append(url); sb.append("', function() { }, function() { });"); sb.append(" }"); return sb.toString(); } }); logoutCallback.setEscapeModelStrings(false); logoutCallback.setOutputMarkupId(true); add(logoutCallback); } public void handleLoginEventCallback(Page p) { Map<String, String[]> map = ((WebRequestCycle) RequestCycle.get()).getRequest().getParameterMap(); String uid = map.get("fbid")[0]; String accessToken = map.get("fbaccesstoken")[0]; if(!((AuthWebSession) getSession()).signInFacebook(accessToken, uid)) { error("Sorry, something went wrong with the Login - please try again."); } } }
Here I add the Javascript-API from Facebook, listen to the login- and logout-triggers and check if the user is already logged in in Facebook but not at my page (so no login-trigger will fire).
If the login is triggered, I extract the interesting data from the response (that is what the Facebook-javascript-request returned to me) and give it to the Session.
The details are inline-commented.
package x.y.z.authentication; import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.wicket.Request; import org.apache.wicket.Session; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.authentication.AuthenticatedWebSession; import org.apache.wicket.authorization.strategies.role.Roles; import org.apache.wicket.injection.web.InjectorHolder; import org.apache.wicket.spring.injection.annot.SpringBean; import com.restfb.DefaultFacebookClient; import com.restfb.FacebookClient; import com.restfb.FacebookException; import com.restfb.types.User; /** * * @author Markus Lachinger <markus.lachinger äT gmail com> * */ public class AuthWebSession extends AuthenticatedWebSession { /** UserID in database */ private long userId = -1; /** facebook-account? */ private boolean facebookAcc = false; /** Access-Token for Facebook-REST-Api */ private String fbToken = null; /** Fixed SerialVersionUID */ private static final long serialVersionUID = 1L; /** Logger */ private Log log = LogFactory.getLog(AuthWebSession.class); /** * @return Current authenticated web session */ public static AuthenticatedWebSession get() { return (AuthenticatedWebSession) Session.get(); } /** * Constructor sets Spring-Injector * * @param request * Webrequest */ public AuthWebSession(Request request) { super(request); InjectorHolder.getInjector().inject(this); //Spring Annotations } /** * Wrapper method for signin in a User via his Facebook-account * * @param accesstoken * @param uid * @return */ public boolean signInFacebook(final String accesstoken, final String uid) { boolean status = authenticateFacebook(accesstoken, uid); signIn(status); return status; } /** * The user logged in at facebook. * * @param accesstoken * Access-Token for OAuth2-REST-Api of Facebook * @param uid * Uique User-ID in facebook * @return successfuly changend Session */ public boolean authenticateFacebook(String accesstoken, String uid) { long uidLong = Long.parseLong(uid); if(isSignedIn() && userId!=uidLong) { //wrong user in current sesson signOut(); replaceSession(); } userId = uidLong; facebookAcc = true; fbToken = accesstoken; FacebookClient fbClient = new DefaultFacebookClient(accesstoken); try { User u = fbClient.fetchObject("me", User.class); //get some data of the User... //i.e. check if user is already in database, add/update entry // user-picture via String picLink = u.getLink()+"/picture" //u.getPic doesn't seem to work for me } catch (FacebookException e) { log.error("Facebook-REST-error for uid:"+ uid +" token:"+ accesstoken, e); return false; } return true; } /** * Authenticates this session using the given email-address and password * * @param email * @param password * @return True if the user was authenticated successfully */ @Override public boolean authenticate(final String email, final String password) { //possibility to do the usual username/password-login } @Override public Roles getRoles() { //get the roles @see AuthenticatedWebSession } /** * @return Id of the user logged in with this session */ public long getUserId() { if (isSignedIn()) { return userId; } else { return -1; } } public void signOut() { super.signOut(); userId=-1; } }
My Session uses AuthenticatedWebSession (auth-roles) as base.
Here I handle the login or logout and take care about things like a different user-ID for already logged-in user (another user logged into Facebook while our page was not refreshed in between)
Just add it like this:
FacebookSignInPanel x = new FacebookSignInPanel("signInPanel"); add(x);
Errors that can occurr:
- Facebook API-Error 100:
Solution: Add a Post-Authorize Rederict URL in you Facebook-App
API Error Code: 100 API Error Description: Invalid parameter Error Message: next is not owned by the application.
see: Scott Murphy's Blog
I hope this is useful for you,
Markus
Updates:
- 2010/6/10:
* Added createPanel() to the constructor. * Added common Bug API Error 100