概要
公開されているQiita APIを使用したQiitaのiOSアプリを作ろうとした時に,認証周りで少し苦戦したのでSwiftを使用したやり方をまとめてみます。Qiita APIを使用したiOSアプリを作ろうとしている人の参考になれば幸いです。
環境
- XCode14.2
- iOS15以上
やり方
Qiita APIを使用した認証手順は以下のようになります。
- Qiitaの認証ページを表示する
- Qiitaの認証ページからアプリにリダイレクトされる
- 2で渡されるコード,事前に発行済みのClientID・ClientSecretを使用してアクセストークンを発行する
上記の手順のやり方を説明していきます。
Qiitaでアプリケーションの登録を行う
にアクセスします。
- アプリケーションの名前
- 自分の作成するアプリケーションの名前
- アプリケーションの説明
- 任意
- 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_id
,client_secret
,code
を渡してアクセストークンを発行するだけです。
最後に
以上でSwiftを使用して,Qiita APIのOAuthを行う説明を終了します。code
を取得するまでの方法が個人的には苦戦したので,その取得方法を主にまとめてみました。