1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[iOS版]QiitaのOAuth認証のやり方

Last updated at Posted at 2023-01-15

概要

公開されているQiita APIを使用したQiitaのiOSアプリを作ろうとした時に,認証周りで少し苦戦したのでSwiftを使用したやり方をまとめてみます。Qiita APIを使用したiOSアプリを作ろうとしている人の参考になれば幸いです。

環境

  • XCode14.2
  • iOS15以上

やり方

Qiita APIを使用した認証手順は以下のようになります。

  1. Qiitaの認証ページを表示する
  2. Qiitaの認証ページからアプリにリダイレクトされる
  3. 2で渡されるコード,事前に発行済みのClientID・ClientSecretを使用してアクセストークンを発行する

上記の手順のやり方を説明していきます。

Qiitaでアプリケーションの登録を行う

にアクセスします。

スクリーンショット 2023-01-15 13.51.26.png

  • アプリケーションの名前
    • 自分の作成するアプリケーションの名前
  • アプリケーションの説明
    • 任意
  • WebサイトのURL
    • 任意
  • リダイレクト先のURL
    • (アプリケーションの名前)://(任意)

を設定して,アプリケーションの登録を行います。

その後,リダイレクト先のURLで指定した(アプリケーションの名前)をCustom URL Schemeで設定します。

を参考に設定しました。

Qiitaの認証ページを表示する

先ほど発行したClientID,ClientSecretを使用してQiitaの認証ページを表示します。
認証ページを表示するには,ASWebAuthenticationSessionを使用します。

などが参考になりました。

実際のコードは以下のようになります。

import Foundation
import AuthenticationServices
import Combine

final class QiitaSignInViewModel: NSObject, ObservableObject {
    private var cancellables = Set<AnyCancellable>()

    private static var authURL: URL {
        var components = URLComponents()
        components.scheme = "https"
        components.host = "qiita.com"
        components.path = "/api/v2/oauth/authorize"
        components.queryItems = [
            "client_id": ClientID,
            "client_secret": ClientSecret,
            "scope": "read_qiita write_qiita"
        ].map { URLQueryItem(name: $0, value: $1) }
        return components.url!
    }
        
    func signIn() {
        let signInPromise = Future<URL, Error> { completion in            
            let authSession = ASWebAuthenticationSession(
                url: QiitaSignInViewModel.authUrl,
                callbackURLScheme: "qiinnreader"
            ) { (url, error) in
                if let error = error {
                    completion(.failure(error))
                } else if let url = url {
                    completion(.success(url))
                }
            }
            
            authSession.presentationContextProvider = self
            authSession.prefersEphemeralWebBrowserSession = true
            authSession.start()
        }
         signInPromise.sink(receiveCompletion: { completion in
            switch completion {
            case .failure(let error):
                print("error: \(error)")
            default:
                break
            }
        }, receiveValue: { _ in
            // アクセストークンを発行する処理
        })
         .store(in: &cancellables)
    }
}

extension QiitaSignInViewModel: ASWebAuthenticationPresentationContextProviding {
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        UIWindow()
    }
}

それぞれ説明していきます。

    private static var authURL: URL {
        var components = URLComponents()
        components.scheme = "https"
        components.host = "qiita.com"
        components.path = "/api/v2/oauth/authorize"
        components.queryItems = [
            "client_id": ArkanaKeys.Global().clientID,
            "client_secret": ArkanaKeys.Global().clientSecret,
            "scope": "read_qiita write_qiita"
        ].map { URLQueryItem(name: $0, value: $1) }
        return components.url!
    }

Qiitaの認証ページを表示するURLを返します。発行したClientID,ClientSecretをそれぞれ渡し,scopeにスペース区切りで文字列で設定することに注意が必要です。 
  

        let signInPromise = Future<URL, Error> { completion in
            let authSession = ASWebAuthenticationSession(
                url: QiitaSignInViewModel.authURL,
                callbackURLScheme: "qiinnreader"
            ) { (url, error) in
                if let error = error {
                    completion(.failure(error))
                } else if let url = url {
                    completion(.success(url))
                }
            }
            
            authSession.presentationContextProvider = self
            authSession.prefersEphemeralWebBrowserSession = true
            authSession.start()
        }

presentationContextProviderでは,認証ページを表示する処理に関して設定します。ここでは,selfを指定しているので,extensionで追加してあげます。

extension QiitaSignInViewModel: ASWebAuthenticationPresentationContextProviding {
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        UIWindow()
    }
}

より,iOS版を想定しているので,UIWindow()を返します。

prefersEphemeralWebBrowserSessionでは,Safariで保存されているデータを共有するかどうかを設定します。デフォルトのfalseのままだとSafariで保存されているcokkieを使用することができるようになってしまう(Safari上でQiitaのログインを行なっていた場合,ログイン処理を行うことなく認証が通るようになる)ので,ここではtrueを設定しておきます。

アクセストークンを発行する

Qiitaの認証ページで認証を行った後はアプリにリダイレクトされ,そのURLにはアクセストークンを発行するのに必要なcodeが含まれています。そのcodeを使用して,アクセストークンの発行を行います。

         signInPromise.sink(receiveCompletion: { completion in
            switch completion {
            case .failure(let error):
                print("error: \(error)")
            default:
                break
            }
        }, receiveValue: { _ in
            // アクセストークンを発行する処理
        })
         .store(in: &cancellables)

において,アクセストークンを発行する処理について説明します。

コードは以下のようになります。

    // 略
         signInPromise.sink(receiveCompletion: { completion in
            switch completion {
            case .failure(let error):
                print("error: \(error)")
            default:
                break
            }
        }, receiveValue: { [self] url in
            guard let code = retrieveCode(url) else {
                return
            }
            createAuthToken(code: code)
        })
         .store(in: &cancellables)
    }

    private func retrieveCode(_ url: URL) -> String? {
        let components: NSURLComponents? = getURLComonents(url)
        for item in components?.queryItems ?? [] {
            if item.name == "code" {
                return item.value?.removingPercentEncoding
            }
        }
        return nil
    }
    
    private func getURLComonents(_ url: URL) -> NSURLComponents? {
        var components: NSURLComponents? = nil
        components = NSURLComponents(url: url, resolvingAgainstBaseURL: true)
        return components
    }

リダイレクト後のurlにはアクセストークンを発行するのに必要なcodeが含まれているので,codeの取得を以下で行います。

    private func retrieveCode(_ url: URL) -> String? {
        let components: NSURLComponents? = getURLComonents(url)
        for item in components?.queryItems ?? [] {
            if item.name == "code" {
                return item.value?.removingPercentEncoding
            }
        }
        return nil
    }
    
    private func getURLComonents(_ url: URL) -> NSURLComponents? {
        var components: NSURLComponents? = nil
        components = NSURLComponents(url: url, resolvingAgainstBaseURL: true)
        return components
    }

を参考にしました。

codeを取得した後は,

に従って,client_idclient_secret,codeを渡してアクセストークンを発行するだけです。

最後に

以上でSwiftを使用して,Qiita APIのOAuthを行う説明を終了します。codeを取得するまでの方法が個人的には苦戦したので,その取得方法を主にまとめてみました。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?