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

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

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

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

Swift

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

解決済

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

tmyk1979
tmyk1979

総合スコア132

Xcode

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

Swift

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

1回答

0評価

0クリップ

376閲覧

投稿2022/02/22 07:59

編集2022/02/25 07:13

前提・実現したいこと

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

良い質問の評価を上げる

以下のような質問は評価を上げましょう

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

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

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

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

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

teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

  • プログラミングに関係のない質問
  • やってほしいことだけを記載した丸投げの質問
  • 問題・課題が含まれていない質問
  • 意図的に内容が抹消された質問
  • 過去に投稿した質問と同じ内容の質問
  • 広告と受け取られるような投稿

評価を下げると、トップページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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も試していなかったかも知れません。 アドバイスをいただき感謝しています。ありがとうございました。

まだ回答がついていません

会員登録して回答してみよう

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

ただいまの回答率
87.20%

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

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

質問する

関連した質問

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

Xcode

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

Swift

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