[iOS]OAuth認証を極める（GitHubAPIで学ぶOAuth認証のフロー）

# アプリでGitHubAPIを使用する

＜対象＞
・OAuth認証のフローを知りたい
・GitHubAPIをつかいたい

GitHubでは、検索やリポジトリの読み書き、プルリクエストの生成・更新・削除などいろいろなAPIが提供されています。
[GitHubAPI](https://developer.github.com/v3/)

これらは
アクセストークンなしでAPIを使用することもできるのですが、その場合はAPIでできることが一部制限されてしまいます。例えば、```SearchAPI```には以下のような注釈があります。

>Note: You must authenticate to search for code across all public repositories.
パブリックなリポジトリを対象に検索を実行するには認証が必要です

というわけで、APIの全機能を使うためにはGitHub認証が必要になるわけです。

本記事ではiOSアプリでGitHub認証しアクセストークンを取得するまで（OAuth認証の流れとアプリ側の実装）をまとめました！

# OAuth認証のフロー

・事前準備
[ここから](https://github.com/settings/developers)GitHubのアプリを新規に作成します。
必要事項を記入し、完了すると```Client ID```と```Client Secret```が発行されます。

でここからがアプリ内での処理です。

① 認可リクエスト
GithubのAPIを使ったアプリのサービスを受けたいので、「認可リクエスト」を行う
→ WebViewなどで認証画面を表示する
→ ユーザーはGithubにログインし、アプリに代理アクセスすることを許可する（連携認証の許可）
→ GitHubからコールバック
→ GitHubから認証コードをレスポンスとしてもらう

② アクセストークン取得
もらったコードを使ってアプリはGithubの「アクセストークン交換場所 (Token Endpoint)」にアクセスします。
その際、認証コードを「アクセストークン」に変換する為に、アプリは事前の登録で取得したクレデンシャル（```Client ID```と```Client Secret```）で認証を受けます。 (クライアント認証)
正しく認証されると、コードと引き換えにアクセストークンをもらいます。

以上の流れは「コードフロー」と呼ばれ、基本的なOAuthのフローです。

図にすると以下のような感じです。
<img width="800" alt="スクリーンショット 2018-05-05 4.31.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/138735/e9bf04a9-8dad-4c03-a20a-122030af69c1.png">

# アプリで実装してみる

流れがわかったので、実装に落とし込んでみます。

（以下ではライブラリとして、Alamofire, Swiftyjsonを利用しています）

## ① 認可リクエスト

```.swift

let gitOuthUrl = URL(string: "https://github.com/login/oauth/authorize?client_id={ 
取得したクライアントID }&scope=public_repo")
let gitOuthRequest = URLRequest(url: gitOuthUrl!)
webView.load(gitOuthRequest)

```
上記では```webView```で認可リクエストを送っています。
（```scope=public_repo```はパブリックなリポジトリに対する読み書き権限を要求していることを示します。なくてもOKです。[参考](https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/)）
で、以下のように連携認証のためにログインが求められます。

<img width="300" alt="Simulator Screen Shot - iPhone X - 2018-05-06 at 00.04.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/138735/6aec82a5-47f2-de7e-8363-915c1d8dbf58.png">

ここでログインを実行するとコールバックURLが実行され、認証コードを取得できるので、それを取得する処理を書きます。

```.swift

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 

        // 設定しているコールバックURLのスキーム、ホスト名で分岐処理       
        if navigationAction.request.url?.scheme == "yyokii", navigationAction.request.url?.host == "GithubSearchApp"{
            dismiss(animated: true, completion: nil)

            // 認証コード取得
            let code = getQueryStringParameter(url: (navigationAction.request.url?.absoluteString)!, param: "code")
            

            // ② アクセストークン取得処理
            let githubAPIManager = GithubAPIManager.sharedManager
            githubAPIManager.getAccessToken(code: code, completion: nil)

            decisionHandler(WKNavigationActionPolicy.allow);
}


static func getQueryStringParameter(url: String, param: String) -> String? {
        guard let url = URLComponents(string: url) else { return nil }
        return url.queryItems?.first(where: { $0.name == param })?.value
    }
```

ここで使用されるコールバックURLはGithHubでアプリ登録したときに設定したURLとなります。
コールバックURLが叩かれたときにクエリパラメーターに付与されている認証コードを取得しています。

## ② アクセストークン取得処理

```.swift

    /// アクセストークンを取得
    ///
    /// - Parameter code: このコードとアクセストークンを交換
    func getAccessToken(code: String!, completion: @escaping () -> Void) {
        self.code = code
        let url = "https://github.com/login/oauth/access_token"
        
        // 受信可能なレスポンスデータのメディアタイプを指定
        let headers = [
            "ACCEPT": "application/json"
        ]
        
        // 認証に必要な値を設定
        let parameters: Parameters = [
            "client_id": self.clientId,
            "client_secret": self.clientSecret,
            "code": self.code
            ]
        
        Alamofire.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON{ response in
            
            switch response.result {
            case .success:
                let json = JSON(response.result.value!)

                // アクセストークンを取得
                let accessToken = json["access_token"].string
                UserDefaults.standard.set(accessToken!, forKey: ACCESS_TOKEN)
                completion()
            case .failure(let error):
                print(error)
            }
        }
    }

```

ヘッダー、リクエストパラメーターを設定し、```"https://github.com/login/oauth/access_token"```をPOST通信で叩きます。でそのレスポンスからアクセストークンを取得しています。


このアクセストークンを使用してAPIを叩くには、ヘッダーに
```"Authorization": "token " + {取得したアクセストークン}```をつければOKです！！


おわり！！！