Facebook 認証で期限切れ accessToken が取得される件

on 2011/09/13 - -

iOS での Facebook 連携の際 accessToken がうまく取得できない問題がありました。

1.事象

認証の際に期限切れの accessToken が取得される。

もう少し詳しく書くと、FBAppAuth という Safari で Facebook のページを開かずに API から直接リクエストを出す方式で認証する場合に発生しました。なぜか3日前に期限の切れた accessToken が取得されるという謎。。

2.回避策

以下の通り Facebook.m を編集して、強制的に Safari での認証方式をとるようにしました。

(認証が必要な時には Safari がアクティブになり、認証後にディアクティブになるという動作になります。)


Facebook.m
- (void)authorize:(NSArray *)permissions
       localAppId:(NSString *)localAppId {
  self.localAppId = localAppId;
  self.permissions = permissions;

//    [self authorizeWithFBAppAuth:YES safariAuth:YES];
    [self authorizeWithFBAppAuth:NO safariAuth:YES];
}



なお通常は FBAppAuth が YES となっていて、アプリをインストールした iPhone が対応していれば(*)FBAppAuth を使って認証を行うようになっています。

* バージョンが 3.4.1 以上の Facebook App がインストールされている場合だそうです

3.補足

Facebook との連携では isSessionValid で Session が有効か判断しますが、判断基準は結局のところ、Facebook インスタンス内に保持している「期限切れ時刻」と「現在時刻」の比較でした。
# accessToken が nil じゃないか、なども確認していますが


Facebook.m

- (BOOL)isSessionValid {
  return (self.accessToken != nil && self.expirationDate != nil
           && NSOrderedDescending == [self.expirationDate compare:[NSDate date]]);

}



感覚的には isSessionValid は accessToken が期限切れでなく、ちゃんと使えることを担保してくれていてほしいのですが、実はそんなことはありませんでした、ということになります。

またここで注目するべきは、「期限切れ時刻」があと何秒間 accessToken が有効かという情報をもとにクライアント側で計算されたものということです。厳密には accessToken と結びついてはいません。

具体的には accessToken 取得時に一緒に受け取る expires_in という秒数を、accessToken 取得時の時刻に足したものが「期限切れ時刻」になっています。

ソースを見ると早いですが、expires_in を引数にした dateWithTimeIntervalSinceNow: で現在時刻からの時刻を取得しています。


Facebook.m
- (BOOL)handleOpenURL:(NSURL *)url {
...
NSString *expTime = [params valueForKey:@"expires_in"];
NSDate *expirationDate = [NSDate distantFuture];
if (expTime != nil) {
  int expVal = [expTime intValue];
  if (expVal != 0) {
    expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
  }
}
...



なので、isSessionValid が YES だったとしても、accessToken の期限が切れていないことは必ずしも保証されません。
# Facebook 側ではちゃんと返す実装になっていると思いますが。

実際、今回の事象では「期限切れ時刻」的には OK である accessToken を http://developers.facebook.com/tools/explorer/ で確認してみると、以下のメッセージが表示されてエラーとなりました。


OAuthException: Error validating access token: Session has expired at unix time xxx. The current unix time is yyy.



原因はまだちゃんと分かっていませんが、以前取得した accessToken と全く同じものを取得しているようなので、なんとなくキャッシュされてるんじゃないか、もしくは全く同じリクエストが飛んでいるんじゃないかと思ってます。Facebook.m か FBRequest.m あたりをちゃんと見たら分かりそうです。

と長々書きましたが、とりあえず FBAppAuth を NO にして、SafariAuth をYES にしておけば、Safari で問題なく認証してくれるので、取り急ぎこの期限切れの accessToken が取得される問題は回避できるようです。

以上。

2 comments :

shntrysh said...

TwitterアプリのためのaccessToken取得でも、Safariでの取得のほうがなにかと簡単だった気がします。。

Facebookでどんなアプリを作る予定ですか?

Masayuki Tanaka said...

なるほど。Safari での認証もそんなに悪くないしね。

最近作ったのは Facebook に写真をアップするアプリ。
便利だと思うけど面白みはあまりないです。笑

いずれ何かソーシャルなアプリかサービス作ってみたいという野望はあるよ。なんか作ろう!