Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'origin/expiringAccessTokens'

  • Loading branch information...
commit 4aa2a663b2b735cc736b4177a1947e176d9f4446 2 parents 0fc1768 + 4969a99
@yariv yariv authored
View
1  sample/Hackbook/Hackbook/APIResultsViewController.h
@@ -19,7 +19,6 @@
@interface APIResultsViewController : UIViewController
<FBRequestDelegate,
-FBSessionDelegate,
UITableViewDataSource,
UITableViewDelegate>{
NSMutableArray *myData;
View
57 sample/Hackbook/Hackbook/HackbookAppDelegate.m
@@ -43,37 +43,44 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[navController.navigationBar setTintColor:[UIColor colorWithRed:0/255.0
green:51.0/255.0
- blue:102.0/255.0
+ blue:102.0/255.0
alpha:1.0]];
[navController.navigationBar setBarStyle:UIBarStyleBlackTranslucent];
self.navigationController = navController;
[rootViewController release];
[navController release];
-
+
// Initialize Facebook
facebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:rootViewController];
-
+
+ // Check and retrieve authorization information
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ if ([defaults objectForKey:@"FBAccessTokenKey"] && [defaults objectForKey:@"FBExpirationDateKey"]) {
+ facebook.accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
+ facebook.expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
+ }
+
// Initialize API data (for views, etc.)
apiData = [[DataSet alloc] init];
-
+
// Initialize user permissions
userPermissions = [[NSMutableDictionary alloc] initWithCapacity:1];
-
+
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
-
+
// Check App ID:
// This is really a warning for the developer, this should not
// happen in a completed app
if (!kAppId) {
- UIAlertView *alertView = [[UIAlertView alloc]
- initWithTitle:@"Setup Error"
- message:@"Missing app ID. You cannot run the app until you provide this in the code."
- delegate:self
- cancelButtonTitle:@"OK"
- otherButtonTitles:nil,
+ UIAlertView *alertView = [[UIAlertView alloc]
+ initWithTitle:@"Setup Error"
+ message:@"Missing app ID. You cannot run the app until you provide this in the code."
+ delegate:self
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil,
nil];
[alertView show];
[alertView release];
@@ -83,7 +90,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
NSString *url = [NSString stringWithFormat:@"fb%@://authorize",kAppId];
BOOL bSchemeInPlist = NO; // find out if the sceme is in the plist file.
NSArray* aBundleURLTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
- if ([aBundleURLTypes isKindOfClass:[NSArray class]] &&
+ if ([aBundleURLTypes isKindOfClass:[NSArray class]] &&
([aBundleURLTypes count] > 0)) {
NSDictionary* aBundleURLTypes0 = [aBundleURLTypes objectAtIndex:0];
if ([aBundleURLTypes0 isKindOfClass:[NSDictionary class]]) {
@@ -91,7 +98,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
if ([aBundleURLSchemes isKindOfClass:[NSArray class]] &&
([aBundleURLSchemes count] > 0)) {
NSString *scheme = [aBundleURLSchemes objectAtIndex:0];
- if ([scheme isKindOfClass:[NSString class]] &&
+ if ([scheme isKindOfClass:[NSString class]] &&
[url hasPrefix:scheme]) {
bSchemeInPlist = YES;
}
@@ -101,21 +108,29 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
// Check if the authorization callback will work
BOOL bCanOpenUrl = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString: url]];
if (!bSchemeInPlist || !bCanOpenUrl) {
- UIAlertView *alertView = [[UIAlertView alloc]
- initWithTitle:@"Setup Error"
- message:@"Invalid or missing URL scheme. You cannot run the app until you set up a valid URL scheme in your .plist."
- delegate:self
- cancelButtonTitle:@"OK"
- otherButtonTitles:nil,
+ UIAlertView *alertView = [[UIAlertView alloc]
+ initWithTitle:@"Setup Error"
+ message:@"Invalid or missing URL scheme. You cannot run the app until you set up a valid URL scheme in your .plist."
+ delegate:self
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil,
nil];
[alertView show];
[alertView release];
}
}
-
+
return YES;
}
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Although the SDK attempts to refresh its access tokens when it makes API calls,
+ // it's a good practice to refresh the access token also when the app becomes active.
+ // This gives apps that seldom make api calls a higher chance of having a non expired
+ // access token.
+ [[self facebook] extendAccessTokenIfNeeded];
+}
+
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
return [self.facebook handleOpenURL:url];
}
View
41 sample/Hackbook/Hackbook/RootViewController.m
@@ -103,7 +103,7 @@ - (void)showLoggedOut {
nameLabel.text = @"";
// Get the profile image
[profilePhotoImageView setImage:nil];
-
+
[[self navigationController] popToRootViewControllerAnimated:YES];
}
@@ -245,19 +245,11 @@ - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
- // Check and retrieve authorization information
- NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
- if ([defaults objectForKey:@"FBAccessTokenKey"]
- && [defaults objectForKey:@"FBExpirationDateKey"]) {
- [delegate facebook].accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
- [delegate facebook].expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
- }
if (![[delegate facebook] isSessionValid]) {
[self showLoggedOut];
} else {
[self showLoggedIn];
}
-
}
- (void)viewWillDisappear:(BOOL)animated {
@@ -315,6 +307,13 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
}
+- (void)storeAuthData:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults setObject:accessToken forKey:@"FBAccessTokenKey"];
+ [defaults setObject:expiresAt forKey:@"FBExpirationDateKey"];
+ [defaults synchronize];
+}
+
#pragma mark - FBSessionDelegate Methods
/**
* Called when the user has logged in successfully.
@@ -323,16 +322,16 @@ - (void)fbDidLogin {
[self showLoggedIn];
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
+ [self storeAuthData:[[delegate facebook] accessToken] expiresAt:[[delegate facebook] expirationDate]];
- // Save authorization information
- NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
- [defaults setObject:[[delegate facebook] accessToken] forKey:@"FBAccessTokenKey"];
- [defaults setObject:[[delegate facebook] expirationDate] forKey:@"FBExpirationDateKey"];
- [defaults synchronize];
-
[pendingApiCallsController userDidGrantPermission];
}
+-(void)fbDidExtendToken:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
+ NSLog(@"token extended");
+ [self storeAuthData:accessToken expiresAt:expiresAt];
+}
+
/**
* Called when the user canceled the authorization dialog.
*/
@@ -359,13 +358,13 @@ - (void)fbDidLogout {
/**
* Called when the session has expired.
*/
-- (void)fbSessionInvalidated {
+- (void)fbSessionInvalidated {
UIAlertView *alertView = [[UIAlertView alloc]
- initWithTitle:@"Auth Exception"
- message:@"Your session has expired."
- delegate:nil
- cancelButtonTitle:@"OK"
- otherButtonTitles:nil,
+ initWithTitle:@"Auth Exception"
+ message:@"Your session has expired."
+ delegate:nil
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil,
nil];
[alertView show];
[alertView release];
View
24 src/Facebook.h
@@ -25,7 +25,7 @@
* and Graph APIs, and start user interface interactions (such as
* pop-ups promoting for credentials, permissions, stream posts, etc.)
*/
-@interface Facebook : NSObject<FBLoginDialogDelegate>{
+@interface Facebook : NSObject<FBLoginDialogDelegate,FBRequestDelegate>{
NSString* _accessToken;
NSDate* _expirationDate;
id<FBSessionDelegate> _sessionDelegate;
@@ -35,6 +35,8 @@
NSString* _appId;
NSString* _urlSchemeSuffix;
NSArray* _permissions;
+ BOOL _isExtendingAccessToken;
+ NSDate* _lastAccessTokenUpdate;
}
@property(nonatomic, copy) NSString* accessToken;
@@ -51,6 +53,12 @@
- (void)authorize:(NSArray *)permissions;
+- (void)extendAccessToken;
+
+- (void)extendAccessTokenIfNeeded;
+
+- (BOOL)shouldExtendAccessToken;
+
- (BOOL)handleOpenURL:(NSURL *)url;
- (void)logout;
@@ -93,8 +101,6 @@
*/
@protocol FBSessionDelegate <NSObject>
-@optional
-
/**
* Called when the user successfully logged in.
*/
@@ -106,13 +112,23 @@
- (void)fbDidNotLogin:(BOOL)cancelled;
/**
+ * Called after the access token was extended. If your application has any
+ * references to the previous access token (for example, if your application
+ * stores the previous access token in persistent storage), your application
+ * should overwrite the old access token with the new one in this method.
+ * See extendAccessToken for more details.
+ */
+- (void)fbDidExtendToken:(NSString*)accessToken
+ expiresAt:(NSDate*)expiresAt;
+
+/**
* Called when the user logged out.
*/
- (void)fbDidLogout;
/**
* Called when the current session has expired. This might happen when:
- * - the access token expired
+ * - the access token expired
* - the app has been disabled
* - the user revoked the app's permissions
* - the user changed his or her password
View
112 src/Facebook.m
@@ -30,6 +30,10 @@
static NSString* kSDK = @"ios";
static NSString* kSDKVersion = @"2";
+// If the last time we extended the access token was more than 24 hours ago
+// we try to refresh the access token again.
+static const int kTokenExtendThreshold = 24;
+
static NSString *requestFinishedKeyPath = @"state";
static void *finishedContext = @"finishedContext";
@@ -93,10 +97,11 @@ - (id)initWithAppId:(NSString *)appId
- (id)initWithAppId:(NSString *)appId
urlSchemeSuffix:(NSString *)urlSchemeSuffix
andDelegate:(id<FBSessionDelegate>)delegate {
-
+
self = [super init];
if (self) {
_requests = [[NSMutableSet alloc] init];
+ _lastAccessTokenUpdate = [[NSDate distantPast] retain];
self.appId = appId;
self.sessionDelegate = delegate;
self.urlSchemeSuffix = urlSchemeSuffix;
@@ -111,6 +116,7 @@ - (void)dealloc {
for (FBRequest* _request in _requests) {
[_request removeObserver:self forKeyPath:requestFinishedKeyPath];
}
+ [_lastAccessTokenUpdate release];
[_accessToken release];
[_expirationDate release];
[_requests release];
@@ -125,11 +131,11 @@ - (void)dealloc {
- (void)invalidateSession {
self.accessToken = nil;
self.expirationDate = nil;
-
+
NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray* facebookCookies = [cookies cookiesForURL:
[NSURL URLWithString:@"http://login.facebook.com"]];
-
+
for (NSHTTPCookie* cookie in facebookCookies) {
[cookies deleteCookie:cookie];
}
@@ -160,6 +166,8 @@ - (FBRequest*)openUrl:(NSString *)url
[params setValue:self.accessToken forKey:@"access_token"];
}
+ [self extendAccessTokenIfNeeded];
+
FBRequest* _request = [FBRequest getRequestWithParams:params
httpMethod:httpMethod
delegate:delegate
@@ -221,7 +229,7 @@ - (void)authorizeWithFBAppAuth:(BOOL)tryFBAppAuth
if (_urlSchemeSuffix) {
[params setValue:_urlSchemeSuffix forKey:@"local_client_id"];
}
-
+
// If the device is running a version of iOS that supports multitasking,
// try to obtain the access token from the Facebook app installed
// on the device.
@@ -319,6 +327,56 @@ - (void)authorize:(NSArray *)permissions {
}
/**
+ * Attempt to extend the access token.
+ *
+ * Access tokens typically expire within 30-60 days. When the user uses the
+ * app, the app should periodically try to obtain a new access token. Once an
+ * access token has expired, the app can no longer renew it. The app then has
+ * to ask the user to re-authorize it to obtain a new access token.
+ *
+ * To ensure your app always has a fresh access token for active users, it's
+ * recommended that you call extendAccessTokenIfNeeded in your application's
+ * applicationDidBecomeActive: UIApplicationDelegate method.
+ */
+- (void)extendAccessToken {
+ if (_isExtendingAccessToken) {
+ return;
+ }
+ _isExtendingAccessToken = YES;
+ NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"auth.extendSSOAccessToken", @"method",
+ nil];
+ [self requestWithParams:params andDelegate:self];
+}
+
+/**
+ * Calls extendAccessToken if shouldExtendAccessToken returns YES.
+ */
+- (void)extendAccessTokenIfNeeded {
+ if ([self shouldExtendAccessToken]) {
+ [self extendAccessToken];
+ }
+}
+
+/**
+ * Returns YES if the last time a new token was obtained was over 24 hours ago.
+ */
+- (BOOL)shouldExtendAccessToken {
+ if ([self isSessionValid]){
+ NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
+ NSDateComponents *components = [calendar components:NSHourCalendarUnit
+ fromDate:_lastAccessTokenUpdate
+ toDate:[NSDate date]
+ options:0];
+
+ if (components.hour >= kTokenExtendThreshold) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+/**
* This function processes the URL the Facebook application or Safari used to
* open your application during a single sign-on flow.
*
@@ -408,7 +466,7 @@ - (BOOL)handleOpenURL:(NSURL *)url {
*/
- (void)logout {
[self invalidateSession];
-
+
if ([self.sessionDelegate respondsToSelector:@selector(fbDidLogout)]) {
[self.sessionDelegate fbDidLogout];
}
@@ -623,6 +681,7 @@ - (void)dialog:(NSString *)action
if ([self isSessionValid]) {
[params setValue:[self.accessToken stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
forKey:@"access_token"];
+ [self extendAccessTokenIfNeeded];
}
_fbDialog = [[FBDialog alloc] initWithURL:dialogURL params:params delegate:delegate];
}
@@ -648,6 +707,8 @@ - (BOOL)isSessionValid {
- (void)fbDialogLogin:(NSString *)token expirationDate:(NSDate *)expirationDate {
self.accessToken = token;
self.expirationDate = expirationDate;
+ [_lastAccessTokenUpdate release];
+ _lastAccessTokenUpdate = [[NSDate date] retain];
if ([self.sessionDelegate respondsToSelector:@selector(fbDidLogin)]) {
[self.sessionDelegate fbDidLogin];
}
@@ -663,4 +724,45 @@ - (void)fbDialogNotLogin:(BOOL)cancelled {
}
}
+#pragma mark - FBRequestDelegate Methods
+// These delegate methods are only called for requests that extendAccessToken initiated
+
+- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
+ _isExtendingAccessToken = NO;
+}
+
+- (void)request:(FBRequest *)request didLoad:(id)result {
+ _isExtendingAccessToken = NO;
+ NSString* accessToken = [result objectForKey:@"access_token"];
+ NSString* expTime = [result objectForKey:@"expires_at"];
+
+ if (accessToken == nil || expTime == nil) {
+ return;
+ }
+
+ self.accessToken = accessToken;
+
+ NSTimeInterval timeInterval = [expTime doubleValue];
+ NSDate *expirationDate = [NSDate distantFuture];
+ if (timeInterval != 0) {
+ expirationDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
+ }
+ self.expirationDate = expirationDate;
+ [_lastAccessTokenUpdate release];
+ _lastAccessTokenUpdate = [[NSDate date] retain];
+
+ if ([self.sessionDelegate respondsToSelector:@selector(fbDidExtendToken:expiresAt:)]) {
+ [self.sessionDelegate fbDidExtendToken:accessToken expiresAt:expirationDate];
+ }
+}
+
+- (void)request:(FBRequest *)request didLoadRawResponse:(NSData *)data {
+}
+
+- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response{
+}
+
+- (void)requestLoading:(FBRequest *)request{
+}
+
@end

0 comments on commit 4aa2a66

Please sign in to comment.
Something went wrong with that request. Please try again.