2013/03/13更新

[XCODE] iOSアプリでOAuth認証を行う、はてブAPIを利用する

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
今日は、iOSアプリケーションでOAuth1.0の認証を行う方法をブログに書きたいと思います。
現在作成しているアプリでOAuth認証が必要になり、実装するまでにいろいろと調べてやっと良いやり方を見つけたので、 その方法を備忘録としても残したいと思います。

画像


OAuth認証とは?iOSアプリでOAuth認証をするには?

OAuth認証とは、iOSアプリからTwitterやFacebookなどのアプリ外の機能を使う際に利用する認証方法の1つです。 OAuth認証を用いることで、ユーザーはアプリに自身のアカウントIDやパスワードを知られずに、Twitterなどの外部機能を利用することができます。 OAuth認証を提供しているAPIは多く、Twitter、Facebook、Evernote、LinkedIn、Google+、はてなブックマークなどいろいろとあります。

OAuth認証にはいくつかの種類があるようでfすが、現在主流なのはOAuth1.0という種類です。
OAuth1.0は自分で実装するのがすごく大変で、ライブラリがないと自分は実装できませんでした。
今回は、gtm-oauthというObjective-Cのライブラリを利用しました。



gtm-oauthを導入する

gtm-oauthをプロジェクトで利用するために、いくつかの手順を実施する必要があります。
以下ではその手順を、順を追って説明していきたいと思います。


ライブラリを入手する

iOSアプリでOAuth認証を行うために、gtm-oauthライブラリを導入します。
以下のように、svnからライブラリのファイルをダウンロードしてきます。
svn checkout http://gtm-oauth.googlecode.com/svn/trunk/
もし、以下のようなエラーが発生する場合には、sudoをつけて実行すると良いかもしれません。
svn: Can't make directory 'trunk': Permission denied
svnがPCにインストールされていない場合には、上記URLにブラウザなどで直接アクセスして、ファイルをダウンロードしてきてもよしです。


上記でダウンロードしたファイルのうち、以下を自身のXCODEプロジェクトに投入します。
  • GTMOAuthAuthentication.h
  • GTMOAuthAuthentication.m
  • GTMOAuthSignIn.h
  • GTMOAuthSignIn.m
  • GTMOAuthViewControllerTouch.h
  • GTMOAuthViewControllerTouch.m
  • GTMOAuthViewTouch

また自分が実装した際には、gtm-oauthが依存している以下のライブラリも必要でしたので、XCODEプロジェクトに追加で投入しました。
svn checkout http://gtm-http-fetcher.googlecode.com/svn/trunk/
上記プロジェクトから以下のファイルを利用します。
  • GTMHTTPFetcher.h
  • GTMHTTPFetcher.m


gtm-oauthが依存するライブラリを追加する

gtm-oauthは、認証情報をkeychainに登録する機能を保持するため、「Security.framework」に依存しています。
そのため、自身のXCODEプロジェクトにSecurity.frameを追加する必要があります。
「Target -> Summary -> Linked Frameworks and Libraries」のところに、Security.frameworkを追加します。
登録した際の画面例は、以下のようになります。
画像


自身のXCODEプロジェクトがARCの場合には、非ARCファイル指定をする

自身のXCODEプロジェクトでARC(Automatic Reference Count=メモリ管理を自動にする機能)を有効にしている場合には、追加で設定が必要です。 gtm-oauthはARCでないファイルのため、そのまま投入してビルドすると、releaseやretainなどの部分でコンパイルエラーになります。 そのため上記ファイルに対して、ARCを利用しない設定をプロジェクトで行う必要があります。
具体的には、「Target -> Build Phases -> Comile Souces」で、上記ファイルのところに「-fno-objc-arc」を指定します。
以下が「-fno-objc-arc」を設定した例です。
画像

これで、OAuth認証のライブラリを利用できるようになります。



OAuth認証を実装する

続いてOAuth認証を実装します。
Twitterへの実装サンプルが、svn:OAuthSampleRootViewControllerTouchがあります。 以下の記述をそのサンプルをもとに作成した、はてなブックマークAPIに対するOAuth認証の実装となります。

今回の実装では、以下3つのファイルを作成してい増す。
  • GTMOAuthSampleViewController.h
  • GTMOAuthSampleViewController.m
  • GTMOAuthSampleViewController.xib
それぞれの実装内容を紹介します。
(GTMOAuthSampleViewController.h)
#import <UIKit/UIKit.h>
#import "GTMOAuthViewControllerTouch.h"
@interface GTMOAuthSampleViewController : UIViewController {
    // 認証情報を保持する変数
    GTMOAuthAuthentication *mAuth;
}

// ボタンタップ時のアクション
-(IBAction)signInHatebuBtnClicked:(id)sender;
-(IBAction)signOutHatebuBtnClicked:(id)sender;
-(IBAction)addHatebuBtnTapped:(id)sender;

-(void)signOut;
-(BOOL)isSignedIn;
-(void)setAuthentication:(GTMOAuthAuthentication *)auth;
@end
ポイントは、GTMOAuthAuthenticationという認証情報を保持するインスタンスをフィールドで保持することで、クラス内で使い回せるようにしています。

(GTMOAuthSampleViewController.mの認証を行う部分)
// はてブAPIに対してOAuth認証をチャレンジします。
-(IBAction)signInHatebuBtnClicked:(id)sender {

	// 認証先URLと権限スコープを指定します。
    NSURL *requestURL = [NSURL URLWithString:@"https://www.hatena.com/oauth/initiate"];
    NSURL *accessURL = [NSURL URLWithString:@"https://www.hatena.ne.jp/touch/oauth/token"];
    NSURL *authorizeURL = [NSURL URLWithString:@"https://www.hatena.com/oauth/authorize"];
    // はてブAPIの場合には、利用する権限をscopeに指定する必要があるため、scopeを指定する。
    NSString *scope = @"write_public";

    // GTMOAuthAuthenticationのインスタンスを作成します。
    mAuth = [self createAuthInformation];

    // set the callback URL to which the site should redirect, and for which
    // the OAuth controller should look to determine when sign-in has
    // finished or been canceled
    //
    // This URL does not need to be for an actual web page; it will not be
    // loaded
    [auth setCallback:@"http://www.yoheim.net/"];

    // OAuthを開始し、ユーザーに入力してもらうためのWebViewを表示します。
    GTMOAuthViewControllerTouch *vc = [[GTMOAuthViewControllerTouch alloc]
                                       initWithScope:scope
                                       language:nil
                                       requestTokenURL:requestURL
                                       authorizeTokenURL:authorizeURL
                                       accessTokenURL:accessURL
                                       authentication:auth
                                       appServiceName:keyChainItemName
                                       delegate:self
                                       finishedSelector:@selector(viewController:finishedWithAuth:error:)];

    // We can set a URL for deleting the cookies after sign-in so the next time
    // the user signs in, the browser does not assume the user is already signed in
    [vc setBrowserCookiesURL:[NSURL URLWithString:@"https://www.hatena.com/"]];
    
    [self.navigationController pushViewController:vc animated:YES];    
}

// GTMOAuthAuthenticationを作成するメソッド
(GTMOAuthAuthentication *)createAuthInformation {
    NSString *comsumerKey = @"KjeicZApK9wztw==";
    NSString *comsumerSecret = @"j7xREmmAT16yJH4vg40rIy7Uaqg=";
    
    GTMOAuthAuthentication *auth = [[GTMOAuthAuthentication alloc]
                                    initWithSignatureMethod:kGTMOAuthSignatureMethodHMAC_SHA1
                                    consumerKey:comsumerKey privateKey:comsumerSecret];
    
    // setting the service name lets us inspect the auth object later to know what service it is for
    [auth setServiceProvider:@"Hatena Bookmark"];    
    return auth;	
}

// 認証終了時のコールバックメソッド
- (void)viewController:(GTMOAuthViewControllerTouch *)viewController finishedWithAuth:(GTMOAuthAuthentication *)auth error:(NSError *)error {
        
    if (error) {
        // Authentication failed (perhaps the user denied access, or closed the
        // window before granting access)
        NSData *responseData = [error.userInfo objectForKey:@"data"];
        NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
        NSLog(@"error occurred. reason = %@", responseString);
        
        mAuth = nil;
    
    } else {
        NSLog(@")Authentication succeeded");
        
        // TokenやTokenSecretを保持したauthを取得するので、それをフィールドに保存します。
        // save the authentication object
        mAuth = auth;
    }
}

これで認証部分が無事にできました。
あと、サインアウトする場合には、以下のように実装します。
-(IBAction)signOutHatebuBtnClicked:(id)sender {
    [GTMOAuthViewControllerTouch removeParamsFromKeychainForName:kKeychainItemName];
    [self setAuthentication:nil];	
}

現在SignInしているかは、以下のように判定することができます。
-(BOOL)isSignedIn {
    return [mAuth canAuthorize];
}



認証が必要なAPIを利用してみる

認証が通れば後は簡単で、以下のようにして認証が必要なAPIを利用することができます。
-(IBAction)addHatebuBtnTapped:(id)sender {
	
	// 認証してなければ、OAuthを開始する。
	if (![self isSignedIn]) {
		[self signInHatebuBtnClicked:nil];
		return;
	}

	// はてブを登録するAPIをコールしてみる。
    // 参考URL
    //    http://developer.hatena.ne.jp/ja/documents/bookmark/apis/atom
    
    // 対象URL
    NSURL *url = [NSURL URLWithString:@"http://b.hatena.ne.jp/atom/post"];
    
    // リクエストの作成
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

    // 2013.03.13 追記
    // 以下のヘッダーを付け忘れてました。
    // 付与しないと、signiture_invalidになる場合があります。
    [request setValue:@"application/x.atom+xml" forHTTPHeaderField:@"Content-Type"];
    
    // 送信データ
    NSString *bodyString
        = [NSString stringWithFormat:@"%@%@%@%@",
            @"<entry xmlns=\"http://purl.org/atom/ns#\"><title>dummy</title>",
            @"<link rel=\"related\" type=\"text/html\" href=\"http://www.yoheim.net/\" />",
            @"<summary type=\"text/plain\">サンプルコメントです</summary>",
            @"</entry>"
           ];
    NSLog(@"xml = %@", bodyString);
    [request setHTTPBody:[bodyString dataUsingEncoding:NSUTF8StringEncoding]];
    
    // ★★ここがgtm-oauthを使った場合のポイントです!!
    // 認証情報の追加
    // この作業は、HTTPBodyやHTTP Headerなどの付与が終わった最後に行う必要があります。
    // これを行った後にリクエストにデータを追加すると、シグニチャ(署名)不正のエラーがかえってきます。
    [request setHTTPMethod:@"POST"];
    [mAuth authorizeRequest:request];
    
    NSError *error;
    NSURLResponse *response;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    
    NSLog(@"error = %@", error);
    NSLog(@"statusCode = %d", ((NSHTTPURLResponse *)response).statusCode);
    NSLog(@"responseText = %@", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
    
    
    // 認証できていない場合には、認証を始めるように実装してみる。
    if (error && error.code == kCFURLErrorUserCancelledAuthentication) {
        NSLog(@"未認証っぽいよー。認証を始めます。");
        [self signInHatebuBtnClicked:nil];
    }
}

こんな感じでiOSでOAuth認証を行うことができます。



最後に

ブログに書くとすんなりだけど、ライブラリの選定から上記実装で処理が成功するまでに1週間弱かかった。。
iOSでのOAuth認証用ライブラリを見つけるのにも一苦労だったし、OAuth1.0を深く理解していなかったため、実装にも一苦労でした。
エラーケースを考えた実装をするなら、もっと実装する必要がありそうだし。

あと、今回gtm-oauthライブラリを使うにあたり、不明な場合にソースコードをたくさん読んだおかげで、すごく勉強になりました。
特にblockプログラミングの部分とか、NSURLConnectionの部分とか学ぶところが多かったので、知識がまとまったらまたブログに書きたいと思います。

このライブラリを使ってはてブ以外のOAuthも行ったので、他の機会に実装内容をブログに書きたいと思います。
最後までご覧頂きましてありがとうございました。





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。