前提・実現したいこと
Swiftを勉強し始めて3ヶ月目に入ったところの初心者です。
フレームワーク・ライブラリに頼らずTwitterAPIを叩いてユーザーに認証してもらい、ユーザーがツイートできるTwitterクライアントアプリを作ろうとしています。
ここに質問の内容を詳しく書いてください。
(例)PHP(CakePHP)で●●なシステムを作っています。
最初のリクエストトークン・リクエストトークンシークレットを取得する所で躓いています。
おそらく署名がおかしいのではないかと思うのですが、どのように直せば良いのかが分からずに困っています。
正しくリクエストの結果を得るにはどうすれば良いか、ご教示いただきたいと思います。
■■な機能を実装中に以下のエラーメッセージが発生しました。
Postでエンドポイントにリクエストを送った所でレスポンスが以下のようなエラーになってしまいます。
発生している問題・エラーメッセージ
Optional("{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}")
該当のソースコード
Swift
import UIKit import CommonCrypto// <------ HMAC-SHA1方式のハッシュ値を作る際に使いました。 class ViewController: UIViewController { @IBOutlet weak var tweetTextView: UITextView! override func viewDidLoad() { super.viewDidLoad() let nonce = (NSUUID().uuidString as NSString).substring(to: 8) let stamp = String(Int64(NSDate().timeIntervalSince1970)) // 設定項目 let api_key = "xxxxxxxxxxxxxxxxxxxxxxxxx" ; // API Key let api_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ; // API Secret let callback_url = "swifter-xxxxxxxxxxxxxxxxxxxxxxxxx://" ; // Callback URL (このプログラムのURLアドレス(以前Swifterを使ってアプリを作ったことがあり、そのままにしています。)) // [アクセストークンシークレット] (まだ存在しないので「なし」) let access_token_secret = "" // エンドポイントURL let request_url = "https://api.twitter.com/oauth/request_token" // リクエストメソッド let request_method = "POST" ; // キーを作成する (URLエンコードする) let api_secret_encode = urlEncode(beforeText: api_secret) let access_token_secret_encode = urlEncode(beforeText: access_token_secret) let signature_key = api_secret_encode + "&" + access_token_secret_encode var params = [ "oauth_callback": callback_url, "oauth_consumer_key": api_key, "oauth_nonce": nonce, "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": stamp, "oauth_version": "1.0", ] let oauth_consumer_key = urlEncode(beforeText: api_key) let oauth_nonce = urlEncode(beforeText: nonce) let oauth_signature_method = urlEncode(beforeText: "HMAC-SHA1") let oauth_timestamp = urlEncode(beforeText: stamp) let oauth_version = urlEncode(beforeText: "1.0") // パラメータの連想配列を[キー=値&キー=値...]の文字列に変換する let request_params = "oauth_callback=\(callback_url)&oauth_consumer_key=\(oauth_consumer_key)&oauth_nonce=\(oauth_nonce)&oauth_signature_method=\(oauth_signature_method)&oauth_timestamp=\(oauth_timestamp)&oauth_version=\(oauth_version)" // 変換した文字列をURLエンコードする let request_params_encode = urlEncode(beforeText: request_params) // リクエストメソッドをURLエンコードする let request_method_encode = urlEncode(beforeText: request_method) // リクエストURLをURLエンコードする let request_url_encode = urlEncode(beforeText: request_url) // リクエストメソッド、リクエストURL、パラメータを[&]で繋ぐ let signature_data = request_method_encode + "&" + request_url_encode + "&" + request_params_encode // キー[$signature_key]とデータ[$signature_data]を利用して、HMAC-SHA1方式のハッシュ値に変換する let hash = signature_data.hmac(key: signature_key) // base64エンコードして、署名[$signature]が完成する let signature = hash.data(using: .utf8)?.base64EncodedString() let signature_encode = urlEncode(beforeText: signature!) // パラメータの連想配列、[$params]に、作成した署名を加える params["oauth_signature"] = signature_encode let callback_url_encode = urlEncode(beforeText: callback_url) let request_params_with_signature = "oauth_consumer_key=\(oauth_consumer_key),oauth_signature_method=\(oauth_signature_method),oauth_timestamp=\(oauth_timestamp),oauth_nonce=\(oauth_nonce),oauth_version=\(oauth_version),oauth_callback=\(callback_url_encode),oauth_signature=\(String(signature_encode))" let urlComponents = URLComponents(string: request_url)! //URLComponentsでURLを生成 var request = URLRequest(url: urlComponents.url!) request.httpMethod = "POST" // Postリクエストを送る(このコードがないとGetリクエストになる) request.allHTTPHeaderFields! = ["Authorization": "OAuth " + request_params_with_signature] print(request.allHTTPHeaderFields!) let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data else { return } let str: String? = String(data: data, encoding: .utf8) print(str) } task.resume() } func urlEncode(beforeText: String) -> String { // RFC3986 に準拠 // 変換対象外とする文字列(英数字と-._~) let allowedCharacters = NSCharacterSet.alphanumerics.union(.init(charactersIn: "-._~")) if let encodedText = beforeText.addingPercentEncoding(withAllowedCharacters: allowedCharacters) { return encodedText } print("encode error") return "" } } extension String { func hmac(key: String) -> String { var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), key, key.count, self, self.count, &digest) let data = Data(_: digest) return data.map { String(format: "%02hhx", $0) }.joined() } }
試したこと
ここに問題に対して試したことを記載してください。
SyncerJP(https://syncer.jp/Web/API/Twitter/REST_API/#section-2-3 )というサイトを参考にしながらコードを書いてみたのですが、例文がSwiftではなくPHPで、PHPはサッパリ分からないためどこをどう直せば良いか分かりません。
他にも「TwitterAPI ツイート 投稿」などで検索したのですが、フレームワークを使っている例文が多く、またその記事も古いせいか参考にはなりませんでした。
PostmanでAPIキーなどを入力して、正しいレスポンスを得られた時のリクエストヘッダーを参考にどこが悪いのか探ったのですが、どうもSyncerのPHPの例文で言う所の「$signature」、つまり署名(?)がPostmanで得られたものよりも倍近く長いのがいけないのではないかと言うところまでは分かったのですが、どのように直せば良いのかが分からずにいます。
・・・追記
OAuthSwiftを使うと良いと言う修正依頼を頂いたので試してみたのですが、
Swift
import UIKit import OAuthSwift class ViewController: UIViewController { // create an instance and retain it var oauthswift = OAuth1Swift( consumerKey: "xxxxxxxxxxxxxxxxxxxxxxxxx", consumerSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", requestTokenUrl: "https://api.twitter.com/oauth/request_token", authorizeUrl: "https://api.twitter.com/oauth/authorize", accessTokenUrl: "https://api.twitter.com/oauth/access_token" ) override func viewDidLoad() { super.viewDidLoad() // authorize _ = oauthswift.authorize( withCallbackURL: "swifter-xxxxxxxxxxxxxxxxxxxxxxxxx://") { result in switch result { case .success(let (credential, _, _)): print(credential.oauthToken) print(credential.oauthTokenSecret) // Do your request case .failure(let error): print(error.localizedDescription) } } } }
・・・でログインできたのですが、コンソールには何もprintされず、リクエストトークン もリクエストトークン シークレットも得られない状態です。ちなみにログイン画面はSafariになっています。
補足情報(FW/ツールのバージョンなど)
ここにより詳細な情報を記載してください。
Xcode11.6
iPhoneSE
iOS13.6
まだ回答がついていません
会員登録して回答してみよう