base64エンコーディングを実装せずにBASIC認証を通す方法

もひとつObjectivc-Cネタ。
Twitter APIのようにBASIC認証がかかっているURLにアクセスする際、NSMutableURLRequestに - setValue:forHTTPHeaderField: でBase64エンコードした文字列をヘッダに突っ込む、という方法しか知らなくてそうしてたのだけど、どうやらそれ以外にも方法があったらしい。

http://username:password@twitter.com/statuses/friends_timeline.json

という方法もあるみたいだけど。。

NSURLConnectionの -connection:didReceiveAuthenticationChallenge: を実装する

NSURLConnectionインスタンスにはアクセスするURLからリクエストを作って普通にリクエストする。

    NSURL *url = [NSURL URLWithString:@"http://twitter.com/statuses/friends_timeline.json"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
    [connection start];

そして、そのdelegateインスタンス(上記ではself)で下記メソッドを実装する。

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    if ([challenge proposedCredential]) {
        [connection cancel];
    } else {
        NSURLCredential *credential = [NSURLCredential credentialWithUser:@"username" password:@"password" persistence:NSURLCredentialPersistenceNone];
        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    }
}

この -connection:didReceiveAuthenticationChallenge: メソッドは、認証が必要な領域にアクセスしようとした際に呼び出されるらしい。
その際に引数で渡される(NSURLAuthenticationChallenge *)challenge は色々な情報を持っているのだけど、そのchallengeのsenderメソッドの返り値は -(id < NSURLAuthenticationChallengeSender >)senderとなっており、このNSURLAuthenticationChallengeSenderプロトコルを持つインスタンスに対して、useCredential:forAuthenticationChallenge: というメソッドを呼び出せる。
このuseCredential:forAuthenticationChallenge: の引数に使うNSURLCredentialインスタンスを作成する際に、credentialWithUser:password:persistence: というクラスメソッドが使えるので、ここの引数にBASIC認証でのusernameとpasswordを渡すと、その先のリクエストではその情報が使われて、無事に認証が通るようになる。この操作が上記コードのelse{}の部分。
この方法でもしパスワードを間違えていたりすると、何度もこのメソッドが繰り返し呼ばれてしまうので、ここでは既にcredentialがセットされているのにこのメソッドが呼び出された場合にはキャンセルするようにしている。


実際にusernameとpasswordをどこで設定するかはシチュエーションによって異なると思う。
例えば設定画面で設定しておいてNSUserDefaultsに保存しておいて上記のメソッド内で読み込んで使うようにするとか、もしくは上記のメソッドが呼び出される度にusernameとpasswordを入力させるダイアログを出すようにする、とか。
なんにせよ、自分でBase64エンコーディングするようなコードを書かずに済むようになるので、結構スマートな方法なんじゃないかと思う。幾つかソースコード公開しているiPhoneTwitterクライアントを見てみたけど、みんなBase64エンコードしてた。。