Instagram APIでOAuth認証する (Swift3版)

# はじめに
弊社で開発しているiOSアプリ[TABLES](https://itunes.apple.com/jp/app/id1209947072)では、Instagramに登録されているユーザ情報を用いてログインする機能を実装しました。Instagramに登録されているユーザ情報を取得するためには、Instagram APIを用いる必要がありましたが、InstagramはAPIを使うためのiOS用のSDKが配布されていなかったため、WebViewを用いて地道に実装しました。

# InstagramのユーザID取得の流れ
以下の手順に沿ってInstagramのユーザIDを取得します
1. Authorization APIで認可コードを発行する
2. ユーザが承認した場合、リダイレクト先に認可コードが返ってくる
3. 認可コードをもとに、ユーザ情報を取得するAPIを叩く
4. ユーザ情報のJSONからユーザIDを取得する

ユーザIDを取得するためには2の認可コードを必ず取得する必要があります。TABLESでは3と4はサーバ側で行ったので、今回は2の**認可コードを取得する**ことをゴールとしています。


## Authorization APIで認可コードを発行する
まずはInstagramから認可コードを発行するためのAuthorization APIを叩きます。下記がURLになります

```
"https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=code"
```

これをWebViewに表示します。今回はUIWebviewではなくWKWebviewを使いました。
WKWebviewは、最初に表示するURLを指定して表示することができるので、以下のようにAuthorization APIを指定して表示します。

```ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()

    let instagramAuthUrl = "https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=code"
        
    let instagramWebView = WKWebView()
    let requestURL = NSURL(string: instagramAuthUrl)
    let request = NSURLRequest(url: requestURL! as URL)
        
    instagramWebView.load(request as URLRequest)
    instagramWebView.navigationDelegate = self
    view = instagramWebView
}
```

すると以下のような画面がWebViewに表示されます。

![43df965e-20e0-a78d-d8de-c3f3829ed9ed.png](https://qiita-image-store.s3.amazonaws.com/0/176572/623fd6f8-8bc2-a90f-3636-aae76af13583.png)


## 認可コードを取得する
次に認可コードを取得します。上の画面でユーザネームとパスワードを入力すると以下のようなページに遷移します。

![ceff3b41-92c1-6e92-4a2b-3fb0facd686c.png](https://qiita-image-store.s3.amazonaws.com/0/176572/6b6f1b84-4fa9-025e-719e-187b81d82f13.png)

ここでユーザがAuthorizeをタップすると、以下のように認可コードがリダイレクト先にパラメータとして送られます。
`REDIRECT-URI?code=CODE`

よって認可コードを取得するためにはWebViewで以下を実装する必要があります。

1. 表示されているページのURIが登録したリダイレクトURIかどうかを判断する
2. URIをパースして認可コードを取得する

以上の機能を実装したソースコードが次のようになります。

```ViewController.swift

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let redirectURL = "[API利用申請時に登録したリダイレクトURL]"
    let URL = navigationAction.request.url.absoluteString!
    
    if Regexp(redirectURL).isMatch(URL ?? "") {
        let comp = NSURLComponents(string: URL)
        let code = comp?.queryItems?[0].value
        // codeが取れているか確認する
        print("---code---")
        print(code ?? "")
    }
}
```
正規表現用のクラスは以下になります。

```Regexp.swift
import Foundation

class Regexp {
    
    let regexp: NSRegularExpression
    let pattern: String
    
    init(_ pattern: String) {
        self.pattern = pattern
        
        do {
            self.regexp = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
        } catch let error as NSError {
            print(error)
            self.regexp = NSRegularExpression()
        }
    }
 
    func isMatch(_ input: String) -> Bool {
        let matches = internalRegexp.matches( in: input, options: [], range:NSMakeRange(0, input.characters.count) )
        return matches.count > 0
    }
    
}
```

> 1.表示されているページのURIが登録したリダイレクトURIかどうかを判断する

WKWebviewの`decidePolicyFor navigationAction`というデリゲートメソッドは、アクセスするURLが変更されるタイミングで呼ばれます。`navigationAction.request.url`でリダイレクト先のURIがとれるので、リダイレクト先のURIが登録したURIかどうかを正規表現で判断しました。

> 2.URIをパースして認可コードを取得する

URIのパースは、NSURLComponentsというオブジェクトによって実装しました。`NSURLComponents.queryItems`とするとクエリパラメータが取得できます。

### 実行結果

```
---code---
3f63e1d6b0356d00976ee5e7d4158ae1
```

認可コードがとれました(上記の認可コードは適当な文字列です)。認可コードを使えば、ユーザIDを取得したり、アクセストークンを用いて投稿を取得したりすることができます。