質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
86.02%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

TwitterAPIのリクエストトークン ・リクエストトークンシークレットを取得したいです。

tmyk1979
tmyk1979

総合スコア145

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

1回答

0グッド

0クリップ

577閲覧

投稿2022/02/22 07:59

編集2022/02/23 15:03

前提・実現したいこと

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

以下のような質問にはグッドを送りましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

グッドが多くついた質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

下記のような質問は推奨されていません。

  • 間違っている
  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

Tomato45

2022/02/22 23:45

CommonCryptoは何のためのものですか?自分がTwitterKitを2019年4月の中旬に試したときはそんなものは使ってません。きっとOAuthSwiftを使ってやれば、簡単にできると思いますっけど。TwitterKitを使った時もUIKitとTwitterKitを使っただけでちゅ。
tmyk1979

2022/02/23 02:54

CommonCryptoはHMAC-SHA1方式のハッシュ値を作る際に使ったものです。 TwitterKitかOAuthSwift・・・ネットに載っている例文が古くエラーや警告に対応できなかったため敬遠していたのですが、もう一度やってみます。 アドバイスありがとうございます。
tmyk1979

2022/02/23 15:07 編集

https://swiftobc-com.translate.goog/repo/OAuthSwift-OAuthSwift-swift-authentication?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=op,sc ・・・を参考にOAuthSwiftを使ったところ、言われた通り簡単にログインできました。 ただ、アクセストークン ・アクセストークン シークレットどころか、コンソールには何もprintされませんでした。 https://qiita.com/haru15komekome/items/10bd68df52ad72233b5c ・・・を参考にやってみたところ、やはりログインはできたのですが、 ただ、リクエストトークン・リクエストシークレットがprintされず、Safari presentedとprintされてしまい、アプリに戻ってくることができませんでした。
Tomato45

2022/02/24 00:15

「...を参考にやってみた」、というのは誰かにそれらのサイトに言って、何がおかしいのかを確認して欲しい、ということですか?自分がOAuthSwiftを使ってTwitter APIを試したのはほぼ3年前でっけど、その時のコードを見る限り、問題があった、という表記はないので、自分には詳しいことはわかりまへん。
tmyk1979

2022/02/24 18:42

初心者にとっては参考サイトの記事を読んだり例文を写経するくらいの事しかできず、記事も古いものが多いため例文もエラーや警告が出る事がほとんどで、そのまま写経したら動いたと言うことは稀です。 「。。。を参考にやってみた」と言うのはサイトに行って何がおかしいのか確認してほしいと言うわけではなく、参考にやってみたところ、(今回は)エラーも警告も出ないけど思った内容がprintされないのが何故か分からないので教えていただきたいと言う意味でした。 TwitterKitはもうサポート終了していますし、OAuthSwiftも試してはいましたがうまく行かなかったので、どうせならそれらに頼らずTwitterAPIを叩けるようになりたいと思い、質問文にも「フレームワークやライブラリに頼らず」と書いたのですが、OAuthSwiftなら簡単とのアドバイスだったのでもう一度試してみようと思った次第です。 なお、Swifterで試してみたところリクエストトークン・シークレットを飛び越してアクセストークン ・シークレットが得られました。目的は果たせるしこれでも良いかなと思うのでこれから自己解決を書こうと思います。 Tomato45さんのアドバイスが無ければSwifterも試していなかったかも知れません。 アドバイスをいただき感謝しています。ありがとうございました。

回答1

1

自己解決

Swifterを使ったところ、リクエストトークン ・リクエストトークンシークレットを飛び越してアクセストークン・アクセストークンシークレットを得られたので、これで良しとします。

Swift

1import UIKit 2import SafariServices 3import Swifter 4 5class ViewController: UIViewController, SFSafariViewControllerDelegate { 6 7 let swifter = Swifter( 8 consumerKey: "xxxxxxxxxxxxxxxxxxxxxxxxx", 9 consumerSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 10 ) 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 print("start") 15 16 swifter.authorize( 17 withCallback: URL(string: "swifter-xxxxxxxxxxxxxxxxxxxxxxxxx://")!, 18 presentingFrom: self, 19 success: { accessToken, response in 20 guard let accessToken = accessToken else { 21 return 22 } 23 let oAuthToken = accessToken.key 24 let secret = accessToken.secret 25 let userID = accessToken.userID 26 let screenName = accessToken.screenName 27 print(oAuthToken) 28 print(secret) 29 print(userID!) 30 print(screenName!) 31 }, failure: { error in 32 print(error) 33 }) 34 } 35}

なお、https://qiita.com/kboy/items/c83a0505d12cc8e7d4b9 を参考にしました。

iOS13以降はSceneDelegate.swiftにも手直しが必要との事。
(引用元の記事へのリンクがうまく貼れませんでした。「【iOS13以降】Swifterを用いた際のcallback URLの記述場所はAppDelegateではない」で検索すれば下記引用元も探せると思います。「かずのアプリときどきキャンプ飯」と言うサイトです。)

引用テキスト

iOS13より前では、コールバックを受ける場合の処理はAppDelegateに

Swift

1import Swifter 2 3 func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 4 return Swifter.handleOpenURL(url, callbackURL: URL(string: "swifter-{Consumer API key}://")!) 5 }

と記載していれば良かったのですが、iOS13以降ではここは呼ばれません。

代わりに、ScheneDelegateに

Swift

1import Swifter 2 3 // callback urlの設定 4 func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>){ 5 Swifter.handleOpenURL(URLContexts.first!.url, callbackURL: URL(string: "swifter-<ConsumerAPIkey>://")!) 6 }

と記載する必要があるので要注意です。

・・・・・アドバイスを頂いたTomato45さんにベストアンサーを付けたい所なのですが、回答ではなく修正依頼のため付けられませんでした。重ねてお礼申し上げます。ありがとうございました。・・・・・

投稿2022/02/24 19:31

tmyk1979

総合スコア145

Tomato45👍を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

Tomato45

2022/02/24 22:13

よく頑張りましたね。自分は特に何もしていません。うまくいったのも皆んなの意見を聞きながら独りで努力したあなたが頑張った結果です。

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
86.02%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています