Ad

Our DNA is written in Swift
Jump

Disabling Facebook SSO, elegantly

The experts are still out as to the motivations behind Facebook’s iOS SDK strategy. But it is rather clear that if Facebook has their way then everybody is to be using their Single Sign-On (SSO) technique. Besides all potential advantages of having this SSO in place it has to leave your app for signing on.

Not exactly something that is useful for all use cases. We have one case (involving ShareKit) which works better with the old style of signing into Facebook. This “traditional approach” shows the login dialog in a web pop up instead of leaving the app.

In this post I’m sharing the 3 methods how to hack the Facebook class and bend it to our will.

When researching for a way to disable SSO and restore the “old style” you inadvertently come across several people telling you to simply hack the authorize: method in Facebook.m.

Original Code Hack

The code for it is Open Source and so that poses little problem for a simple modification. You simply replace the method with the version below:

- (void)authorize:(NSArray *)permissions 
{
	[self setValue:permissions forKey:@"permissions"];
 
	[self authorizeWithFBAppAuth:NO safariAuth:NO];
}

But unfortunately that means you’ll have to modify code that is owned by somebody else. And I’m not talking about licensing concerns but the simple fact that if Facebook updates the SDK you have to do this change every time after updating to the latest version.

I am using the Facebook SDK via a submodule in ShareKit and I generally want to avoid having to keep track of modifications in third-party code. Which brings us to method number 2.

Category Hack

We know that we can add methods to existing classes by simply adding them in categories. If you have a method that has the same name as an existing method then the new method simply replaces the old one. So we can wrap the above hack in a category like so:

Facebook+NoSSO.h

#import "Facebook.h"
 
@interface Facebook (NoSSO)
 
- (void)authorize:(NSArray *)permissions;
 
@end

Facebook+NoSSO.m

#import "Facebook+NoSSO.h"
 
#import "Facebook.h"
 
@interface Facebook ()
 
- (void)authorizeWithFBAppAuth:(BOOL)tryFBAppAuth
                    safariAuth:(BOOL)trySafariAuth;
@end
 
@implementation Facebook (NoSSO)
 
- (void)authorize:(NSArray *)permissions 
{
	[self setValue:permissions forKey:@"permissions"];
 
	[self authorizeWithFBAppAuth:NO safariAuth:NO];
}
 
@end

Notice that we need to define the authorizeWithFBAppAuth:safariAuth: in an anonymous category so that the compiler accepts us calling it from our own authorize: method. Without that we would get a compiler warning. This method is internal to the Facebook class and not exposed via the header.

This method has two problems: 1) I don’t think that Apple actually guarantees a certain order in which classes and categories are loaded. So this is a bit risky to rely on it having always been working. 2) As of Xcode 4.3.2 the compiler will actually warn you “Category is implementing a method which will also be implemented by its primary class”

Which brings us to the third – most elegant method.

Swizzeling Hack

Being dynamic in nature the Objective-C runtime allows us to dynamically changes classes and methods which our app is running. One such technique is called “Method Swizzeling” which basically means to exchange two methods. With this method you can substitute your own method implantation.

First I borrowed some code from ShareKit and adapted it for general use in DTFoundation:

NSObject+DTRuntime.h

@interface NSObject (DTRuntime)
 
+ (void)swizzleMethod:(SEL)selector withMethod:(SEL)otherSelector;
 
@end

NSObject+DTRuntime.m

#import <objc/runtime.h>
 
@implementation NSObject (DTRuntime)
 
+ (void)swizzleMethod:(SEL)selector withMethod:(SEL)otherSelector
{
	// my own class is being targetted
	Class c = [self class];
 
	// get the methods from the selectors
	Method originalMethod = class_getInstanceMethod(c, selector);
	Method otherMethod = class_getInstanceMethod(c, otherSelector);
 
	if (class_addMethod(c, selector, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod)))
	{
		class_replaceMethod(c, otherSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
	}
	else
	{
		method_exchangeImplementations(originalMethod, otherMethod);
	}
}
 
@end

That takes care of the actual exchanging. I have to admit that I don’t quite understand the if, because in my tests it always ended up in the method_exchangeImplementations part. But as long as it is working… šŸ˜‰

Now we have to only have a method with a unique name (to avoid the warning) and then we can exchange this method for the original authorize: method by means of this swizzle. The modified category now looks like this:

Facebook+NoSSO.h

#import "Facebook.h"
 
@interface Facebook (iCatalog)
 
- (void)authorize_noSSO:(NSArray *)permissions;
 
+ (void)toggleSingleSignOn;
 
@end

Facebook+NoSSO.m

#import "Facebook+iCatalog.h"
#import "NSObject+DTRuntime.h"
 
@interface Facebook ()
 
- (void)authorizeWithFBAppAuth:(BOOL)tryFBAppAuth
                    safariAuth:(BOOL)trySafariAuth;
@end
 
@implementation Facebook (iCatalog)
 
- (void)authorize_noSSO:(NSArray *)permissions 
{
	[self setValue:permissions forKey:@"permissions"];
 
	[self authorizeWithFBAppAuth:NO safariAuth:NO];
}
 
+ (void)toggleSingleSignOn
{
	[Facebook swizzleMethod:@selector(authorize:) withMethod:@selector(authorize_noSSO:)];
}
 
@end

You see that I have also added a class method to toggle SSO on and off. I named it “toggle” because each call will exchange the two method implementations. While the first toggle will disable SSO the second call restores the original method and thus re-enables it.

With this you only have to call the toggleSingleSignOn once per app launch, the best place is the app delegate’s +initialize which is guaranteed to be called just once and also in a thread-safe manner.

@implementation MyAppDelegate
+ (void)initialize
{
	// disable Facebook SSO
	[Facebook toggleSingleSignOn];
}
@end

That’s all it takes.

Conclusion

Of the three ways how you can modify somebody else’s code’s behavior the one that actually replaces the implementation via swizzeling is the most convenient and least dangerous, at least if you know what you are doing.

This alternative does not have the drawback of original code hacking that you incur an additional maintenance overhead. And it does not have the drawback of compiler warnings for the category hack approach.


Categories: Recipes

1 Comment »

  1. This doesn’t seem to work for the latest Facebook iOS SDK (3.1.1). The header files are different now. For one project I only use the Facebook SDK (not ShareKit). For a different project, I actually use ShareKit, in which I also use the GitHub submodule method for it’s Facebook SDK. But this appears to be a different version compared to what you get when you install the official SDK (as Framework) from the Facebook Developers site. Thoughts?