MobileSDKを使わずにSalesforceにアクセスするiPhoneアプリを作る【認証編】

[Mobile SDK](http://wiki.developerforce.com/page/JP:Mobile_SDK)を使うとSalesforceにアクセスするモバイルアプリケーションが簡単に作れそうですが、単一の組織にログインするのが前提なため要件に合わず、今回はMobile SDKを使わずに作ってみましたので基本事項をまとめておきます(XcodeでのiPhoneアプリ開発の知識があるの前提です)。

Salesforceの認証(OAuth2.0)については、こちらをまず読んでおくと分かりやすいです。
https://wiki.developerforce.com/page/JP:Digging_Deeper_into_OAuth_2.0_at_Salesforce.com

# 接続アプリケーションの登録
開発用のSalesforce組織にログインし、「設定＞アプリケーション」から接続アプリケーションを登録します。
ここで発行される「コンシューマ鍵」(=client_id)が認証の実装に必要です。
![接続アプリケーション](https://qiita-image-store.s3.amazonaws.com/0/9490/fb9e9787-2011-fd66-d4cd-8b48828137ec.png)


# StoryboardでUIの大枠を作る
まずは簡単に、認証してユーザ情報を表示するだけのアプリを作ります。
新規プロジェクトで、Master-Detail Applicationをひな形に、

* Master ViewにはSign In用のボタン代わりのセルと、取得したユーザ情報を表示する用のセルをstatic cellとして置きました。
* Detail Viewには認証のページを表示する為のUIWebViewを置きました。

![Storyboard](https://qiita-image-store.s3.amazonaws.com/0/9490/e65b156a-28e3-3f48-a707-e119f273df2f.png)

# OAuthでの認証
UIWebViewの初期表示として、認証画面に飛ばします。
クライアントアプリケーション用の認証方式、User-Agent Flowを使うため、`response_type=token`を指定します。

```objc:SEDetailViewController.m
- (void)viewDidLoad
{
    [super viewDidLoad];

    // OAuthの認証画面を初期表示(User-Agent Flow)
    NSString *authorizeURL = @"https://login.salesforce.com/services/oauth2/authorize?response_type=token&client_id=3MVG9I1kFE5Iul2Cbahhzus9WVFyGSQGrQ3gTYeMzkJGbA_43hAg0fb57FTxZkWASpInGI4lSg3gpzlL4WErb&redirect_uri=test%3A%2F%2Fcallback&display=touch";
    NSURLRequest *req = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:authorizeURL]];

    _webview.delegate = self;
    [_webview loadRequest:req];
}
```

認証が終わった時の`test://callback`の処理をhookし、パラメータで返ってくる認証情報をバラします。
取得したaccess_tokenを使用して、idサービスでユーザの情報を取得します。

```objc:SEDetailViewController.m
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *absoluteURL = request.URL.absoluteString;
    
    // callback先以外の場合は通常のページ遷移
    if ([absoluteURL rangeOfString:@"test://callback"].location == NSNotFound) return YES;

    // #以降のパラメータをバラす
    NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:@"(\\w+)=([^&]+)" options:0 error:nil];
    NSArray *matches = [re matchesInString:absoluteURL options:0 range:NSMakeRange(0, absoluteURL.length)];
    NSMutableDictionary *authInfo = @{}.mutableCopy;
    
    for (NSTextCheckingResult *r in matches) {
        NSString *k = [absoluteURL substringWithRange:[r rangeAtIndex:1]];
        NSString *v = [absoluteURL substringWithRange:[r rangeAtIndex:2]];
        authInfo[k] = [v stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
    }
    
    // User-Agent Flowなのでaccess_tokenが直接返ってくる
    NSString *accessToken = authInfo[@"access_token"];

    // 取得したaccess_tokenを使ってidサービスに接続。ユーザ情報を取得する
    
    // access_tokenをAuthorizationヘッダへ
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPAdditionalHeaders = @{@"Authorization": [NSString stringWithFormat:@"Bearer %@", accessToken]};
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    
    // idサービスのURLは'id'パラメータで返ってくる
    NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:authInfo[@"id"]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        // 取得したユーザ情報をMain Viewに渡して画面戻る
        NSDictionary *idInfo = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // メインスレッドで実行
            [self performSegueWithIdentifier:@"oauthSuccess" sender:@{@"authInfo":authInfo, @"idInfo":idInfo}];
        }];
    }];
    [task resume];
    
    return NO;
}
```

※[ソースコード全体はこちら](https://github.com/pomu0325/SalesforceMobileExample/tree/1861458449b328674b72e00c4f3bbe5ef75896ad/SalesforceExample)

[動かしてみるとこんな感じです(YouTube)](https://www.youtube.com/watch?v=kRWMfyCQkLQ)


エラー処理等は特に入れてない基本部分のみですが、これでaccess_tokenを取得し、APIの呼び出しができました。