前提・実現したいこと
Swiftを勉強し始めて3ヶ月目に入ったところの初心者です。
フレームワーク・ライブラリに頼らずTwitterAPIを叩いてユーザーに認証してもらい、ユーザーがツイートできるTwitterクライアントアプリを作ろうとしています。
ここに質問の内容を詳しく書いてください。
(例)PHP(CakePHP)で●●なシステムを作っています。
最初のリクエストトークン・リクエストトークンシークレットを取得する所で躓いています。
おそらく署名がおかしいのではないかと思うのですが、どのように直せば良いのかが分からずに困っています。
正しくリクエストの結果を得るにはどうすれば良いか、ご教示いただきたいと思います。
■■な機能を実装中に以下のエラーメッセージが発生しました。
Postでエンドポイントにリクエストを送った所でレスポンスが以下のようなエラーになってしまいます。
発生している問題・エラーメッセージ
Optional("{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}")
該当のソースコード
Swift
1import UIKit 2import CommonCrypto// <------ HMAC-SHA1方式のハッシュ値を作る際に使いました。 3 4class ViewController: UIViewController { 5 6 7 @IBOutlet weak var tweetTextView: UITextView! 8 9 override func viewDidLoad() { 10 super.viewDidLoad() 11 12 let nonce = (NSUUID().uuidString as NSString).substring(to: 8) 13 let stamp = String(Int64(NSDate().timeIntervalSince1970)) 14 // 設定項目 15 let api_key = "xxxxxxxxxxxxxxxxxxxxxxxxx" ; // API Key 16 let api_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ; // API Secret 17 let callback_url = "swifter-xxxxxxxxxxxxxxxxxxxxxxxxx://" ; // Callback URL (このプログラムのURLアドレス(以前Swifterを使ってアプリを作ったことがあり、そのままにしています。)) 18 19 // [アクセストークンシークレット] (まだ存在しないので「なし」) 20 let access_token_secret = "" 21 22 // エンドポイントURL 23 let request_url = "https://api.twitter.com/oauth/request_token" 24 25 // リクエストメソッド 26 let request_method = "POST" ; 27 28 // キーを作成する (URLエンコードする) 29 let api_secret_encode = urlEncode(beforeText: api_secret) 30 let access_token_secret_encode = urlEncode(beforeText: access_token_secret) 31 32 let signature_key = api_secret_encode + "&" + access_token_secret_encode 33 34 var params = [ 35 "oauth_callback": callback_url, 36 "oauth_consumer_key": api_key, 37 "oauth_nonce": nonce, 38 "oauth_signature_method": "HMAC-SHA1", 39 "oauth_timestamp": stamp, 40 "oauth_version": "1.0", 41 ] 42 43 let oauth_consumer_key = urlEncode(beforeText: api_key) 44 let oauth_nonce = urlEncode(beforeText: nonce) 45 let oauth_signature_method = urlEncode(beforeText: "HMAC-SHA1") 46 let oauth_timestamp = urlEncode(beforeText: stamp) 47 let oauth_version = urlEncode(beforeText: "1.0") 48 49 // パラメータの連想配列を[キー=値&キー=値...]の文字列に変換する 50 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)" 51 52 // 変換した文字列をURLエンコードする 53 let request_params_encode = urlEncode(beforeText: request_params) 54 55 // リクエストメソッドをURLエンコードする 56 let request_method_encode = urlEncode(beforeText: request_method) 57 58 // リクエストURLをURLエンコードする 59 let request_url_encode = urlEncode(beforeText: request_url) 60 61 // リクエストメソッド、リクエストURL、パラメータを[&]で繋ぐ 62 let signature_data = request_method_encode + "&" + request_url_encode + "&" + request_params_encode 63 64 // キー[$signature_key]とデータ[$signature_data]を利用して、HMAC-SHA1方式のハッシュ値に変換する 65 let hash = signature_data.hmac(key: signature_key) 66 67 // base64エンコードして、署名[$signature]が完成する 68 let signature = hash.data(using: .utf8)?.base64EncodedString() 69 70 let signature_encode = urlEncode(beforeText: signature!) 71 // パラメータの連想配列、[$params]に、作成した署名を加える 72 params["oauth_signature"] = signature_encode 73 74 let callback_url_encode = urlEncode(beforeText: callback_url) 75 76 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))" 77 78 let urlComponents = URLComponents(string: request_url)! //URLComponentsでURLを生成 79 var request = URLRequest(url: urlComponents.url!) 80 request.httpMethod = "POST" // Postリクエストを送る(このコードがないとGetリクエストになる) 81 request.allHTTPHeaderFields! = ["Authorization": "OAuth " + request_params_with_signature] 82 print(request.allHTTPHeaderFields!) 83 let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 84 guard let data = data else { return } 85 let str: String? = String(data: data, encoding: .utf8) 86 print(str) 87 } 88 task.resume() 89 } 90 91 func urlEncode(beforeText: String) -> String { 92 // RFC3986 に準拠 93 // 変換対象外とする文字列(英数字と-._~) 94 let allowedCharacters = NSCharacterSet.alphanumerics.union(.init(charactersIn: "-._~")) 95 96 if let encodedText = beforeText.addingPercentEncoding(withAllowedCharacters: allowedCharacters) { 97 return encodedText 98 } 99 print("encode error") 100 return "" 101 } 102} 103 104extension String { 105 106 func hmac(key: String) -> String { 107 var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) 108 CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), key, key.count, self, self.count, &digest) 109 let data = Data(_: digest) 110 return data.map { String(format: "%02hhx", $0) }.joined() 111 } 112 113} 114
試したこと
ここに問題に対して試したことを記載してください。
SyncerJP(https://syncer.jp/Web/API/Twitter/REST_API/#section-2-3 )というサイトを参考にしながらコードを書いてみたのですが、例文がSwiftではなくPHPで、PHPはサッパリ分からないためどこをどう直せば良いか分かりません。
他にも「TwitterAPI ツイート 投稿」などで検索したのですが、フレームワークを使っている例文が多く、またその記事も古いせいか参考にはなりませんでした。
PostmanでAPIキーなどを入力して、正しいレスポンスを得られた時のリクエストヘッダーを参考にどこが悪いのか探ったのですが、どうもSyncerのPHPの例文で言う所の「$signature」、つまり署名(?)がPostmanで得られたものよりも倍近く長いのがいけないのではないかと言うところまでは分かったのですが、どのように直せば良いのかが分からずにいます。
・・・追記
OAuthSwiftを使うと良いと言う修正依頼を頂いたので試してみたのですが、
Swift
1 2import UIKit 3import OAuthSwift 4 5class ViewController: UIViewController { 6 // create an instance and retain it 7 var oauthswift = OAuth1Swift( 8 consumerKey: "xxxxxxxxxxxxxxxxxxxxxxxxx", 9 consumerSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 10 requestTokenUrl: "https://api.twitter.com/oauth/request_token", 11 authorizeUrl: "https://api.twitter.com/oauth/authorize", 12 accessTokenUrl: "https://api.twitter.com/oauth/access_token" 13 ) 14 15 override func viewDidLoad() { 16 super.viewDidLoad() 17 // authorize 18 _ = oauthswift.authorize( 19 withCallbackURL: "swifter-xxxxxxxxxxxxxxxxxxxxxxxxx://") { result in 20 switch result { 21 case .success(let (credential, _, _)): 22 print(credential.oauthToken) 23 print(credential.oauthTokenSecret) 24 // Do your request 25 case .failure(let error): 26 print(error.localizedDescription) 27 } 28 } 29 } 30}
・・・でログインできたのですが、コンソールには何もprintされず、リクエストトークン もリクエストトークン シークレットも得られない状態です。ちなみにログイン画面はSafariになっています。
補足情報(FW/ツールのバージョンなど)
ここにより詳細な情報を記載してください。
Xcode11.6
iPhoneSE
iOS13.6

回答1件
あなたの回答
tips
プレビュー