# Twitter アプリ

ここでは、Twitter 上でのOAuthを利用したシングルサインオン ( Single Sign-On/SSO ) の方法を解説します。 シングルサインオンには、[InAppBrowser](/reference/core-cordova-plugins/cordova_10.0/inappbrowser.md) プラグインと、サードパーティ製 [advanced-http](https://www.npmjs.com/package/cordova-plugin-advanced-http) プラグイン を使用し、また、OAuthに必要なシグネチャは [oauth-signature](https://www.npmjs.com/package/oauth-signature) ライブラリ を利用します。

認証成功後、ユーザーの基本情報をアプリ上に表示したり、ツイートを投稿することが出来ます。

{% hint style="info" %}
サードパーティー製 Cordova プラグインを確認する場合は、カスタムビルドデバッガー ( [Android 版](/products_guide/debugger/installation/debugger_android.md#kasutamubirudo-monaca-debaggnobirudotoinsutru) または [iOS 版](/products_guide/debugger/installation/debugger_ios.md#kasutamubirudo-monaca-debaggnobirudo) ) を作成する必要があります。
{% endhint %}

## デモ

[<img src="https://docs.monaca.io/images/common/import_img.png" alt="" data-size="line">プロジェクトをインポート](https://monaca.mobi/directimport?pid=5ff83196e78885a728ca7a23)

**テスト環境**

* Android 11.0
* iOS 14.3

![](/files/-MgIsqPhCNzOW4XGzhwB)

## 事前準備

### Twitter の Consumer Key と Consumer Secret の確認方法

TwitterAPIを利用するためには、デベロッパーアカウントの登録が必要となります。\
[Twitter Developer Account](https://developer.twitter.com/en/apply-for-access) よりデベロッパーアカウントを登録します。

次に、『 Twitter Developer Portal 』ページ上で、アプリを登録し、`Consumer Key` (`API key` ) と `Consumer Secret` (`API secret key`) 、`Bearer token` を発行します。

1. [Twitter Developer Portal の概要ページ](https://developer.twitter.com/en/portal/projects-and-apps) へ移動します。
2. `Standalone Apps` を、画面下部の `Create App` ボタンより作成します。\
   （`Standalone Apps` を作成する代わりに、Projects配下にAppsを作成でも構いません）
3. Name ( アプリ名 )、Description ( アプリの説明 )、Website ( アプリのダウンロード元となる URL )を入力します。(※ アプリ名には、すでに利用されている名前は利用できません。また、１日あたりに作成出来るアプリ数には、上限があります。)
4. 「Callback URL」(任意：認証成功後に表示されるページ。)を入力します。\
   今回のサンプルアプリでは、 `mymonacaapp://` と設定します。適時、自分のアプリ用のものに変更してください。\
   なお、このCallback URLはアプリの実装時にも必要になります。
5. `Settings` タブを選択して、 `App permissions` から、 `Read and Write` 権限を許可します。アプリからツイートを行わない場合は、`Read` 権限のみ許可します。
6. `Settings` タブを選択して、 `Authentication` から、 `3-legged OAuth` を有効にします。

## プラグインのインポート

シングルサインオンを実現させるため、[InAppBrowser](/reference/core-cordova-plugins/cordova_10.0/inappbrowser.md) プラグインと サードパーティ製 [advanced-http](https://www.npmjs.com/package/cordova-plugin-advanced-http) プラグイン を使用します。 また、OAuthに必要なシグネチャを作成するために [oauth-signature](https://www.npmjs.com/package/oauth-signature)ライブラリを使用します。

1. Monaca クラウド IDE から **`設定 → Cordova プラグインの管理`** を選択します。
2. `InAppBrowser` プラグインを `有効` にします。  &#x20;

![](/files/-MgIv0B0x0tZjXfYbLyK)

&#x20;  3\. ボタン「Cordovaプラグインのインポート」をクリックして、「URLもしくはパッケージ名を指定します」にチェックをし、パッケージ名に `cordova-plugin-advanced-http` を入力し、OKボタンをクリックします。

## JSコンポーネントのインポート

1. Monaca クラウド IDE から **`設定 → JS/CSSコンポーネントの追加と削除`** を選択します。
2. コンポーネントの検索フォームに `oauth-signature-js` と入力して検索。検索結果から `oauth-signature-js` をインストールします。その後、読み込み対象の選択にて、 `components/oauth-signature-js/dist/oauth-signature.js` を選択し、保存します。 なお、`oauth-signature.js`ライブラリは、 *BSD-3-Clause* であるので、アプリのソースコード、バイナリコードの配布を行う場合はご注意下さい。

## アプリの解説

### ファイル構成

![](/files/-MgIwftGo_mmGIiB3mK0)

| ファイル            | 説明                                 |
| --------------- | ---------------------------------- |
| `index.html`    | アプリ画面のページ                          |
| `css/style.css` | アプリのスタイルシート                        |
| `js/app.js`     | アプリの実行時にさまざまな処理を行う JavaScript ファイル |

### HTML の解説

#### index.html

```markup
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: content: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
    <script src="components/loader.js"></script>
    <link rel="stylesheet" href="components/loader.css">
    <link rel="stylesheet" href="css/style.css">
    <script src="js/app.js"></script>

</head>
<body>
    <h2>Twitter Sample</h2>
    <div>
        <button onclick="connect()">Connect !</button>
        <p>Your Id:
            <span id="tw-id"></span>
        </p>
        <p>Your Name:
            <span id="tw-name"></span>
        </p>
        <hr>
        <button id="showMe" onclick="showMe()">Show Me</button>
        <p>Your Info:
            <span id="tw-profile"></span>
        </p>
        <img id="tw-profile-image" class="profile">
        <hr>
        <p>
        <textarea id="tweetText" rows=5 cols=40>Hello</textarea>
        </p>
        <button id="tweetBtn" onclick="sendTweet()">Send Tweet</button>
    </div>
</body>
</html>
```

アプリ画面となるページです。

このページは、大きく３つのブロックに分かれています。それぞれのブロックは、 `<hr>` タグにより区切られています

1. ログイン ブロック:

   Twitterログイン画面に移行するボタンと、ログイン後にユーザーID、ユーザー名（スクリーン名）を表示するコンポーネントがあります。
2. プロフィール ブロック:

   ログインユーザーのプロフィールを取得するボタンと、取得成功後にそれを表示するコンポーネントがあります。
3. ツイート　ブロック：

   テキストエリアと、ツイートするボタンがあります。

### JavaScript の解説

#### app.js

```javascript
  const apiKey = '【Consumer Key（API key）】';
  const secretKey = '【Consumer Secret （API key secret）】'; 
  const callbackURL = '【登録したコールバックURL（スキーム）】';

  const signatureMethod = "HMAC-SHA1";
  const version = "1.0";

  const requestTokenURL = "https://api.twitter.com/oauth/request_token";
  const loginURL = "https://api.twitter.com/oauth/authorize"
  const accessTokenURL = "https://api.twitter.com/oauth/access_token";
  const updateURL = "https://api.twitter.com/1.1/statuses/update.json";
  const usersShowURL = "https://api.twitter.com/1.1/users/show.json";

  let model = {};

  function percentEncode(str) {
      return encodeURIComponent(str).replace(/[!'()*]/g, char => '%' + char.charCodeAt().toString(16));
  }

  function getNonce() {
      const array = new Uint8Array(32);
      window.crypto.getRandomValues(array);
      return Array.from(array).map(uint => uint.toString(16).padStart(2, '0')).join('');
  }

  function oauthSend(url, method, accessTokenSecret, data, oauth_params, cb) {
      const timestamp = (Math.floor(Date.now() / 1000)).toString(10);
      const parameters = Object.assign(
          {},
          oauth_params,
          {
              oauth_nonce: getNonce(),
              oauth_signature_method: signatureMethod,
              oauth_timestamp: timestamp,
              oauth_version: version
          },
          data
      );
      const signature = oauthSignature.generate(method.toUpperCase(),
          url,
          parameters,
          secretKey,
          accessTokenSecret
      );
      const authorizationHeader = "OAuth " + Object.keys(parameters).map((key) => {
          return percentEncode(key) + '="' + percentEncode(parameters[key]) + '", '
      }).join('') + 'oauth_signature=\"' + signature + '\"';

      const urlWithParams = method.toUpperCase() === "GET" ?
          url + "?" + Object.keys(data).map(function(k) {
              return encodeURIComponent(k) + '=' + encodeURIComponent(data[k])
          }).join('&') : url;

      cordova.plugin.http.sendRequest(urlWithParams, {
          method: method,
          headers: {'Authorization': authorizationHeader},
          data: data,
          serializer: null
      }, (res) => {
          cb(res);
      }, (error) => {
          alert(error.error);
      });
  }

  function connect() {
      oauthSend(requestTokenURL, 'post', "", {}, { 
          oauth_callback: callbackURL,
          oauth_consumer_key: apiKey,
      }, function (res) {
          openLoginDialog(res.data);
      });
  }

  function openLoginDialog(res) {
      const oauth = res.split('&')[0];
      const url = loginURL + "?" + oauth;
      const ref = cordova.InAppBrowser.open(url, "_blank", 'location=yes,beforeload=get');
      ref.addEventListener('beforeload', beforeLoad(ref));
  }

  function beforeLoad(ref) {
      return function (event, cb) {
          if (event.url && event.url.startsWith(callbackURL) ) {
              const url = new URL(event.url);
              const params = Array.from(url.searchParams.entries()).reduce(
                  function (acc, cur) {
                      acc[cur[0]] = cur[1];
                      return acc;
                  } , {} 
              );
              ref.close();
              getAccessToken(params);
          } else {
              cb(event.url);
          }
      };
  }

  function getAccessToken(params) {
      oauthSend(accessTokenURL, 'post', "", {}, { 
          oauth_verifier: params.oauth_verifier,
          oauth_token: params.oauth_token,
          oauth_consumer_key: apiKey,
      }, function (res) {
          const params = Array.from(new URLSearchParams(res.data).entries()).reduce(
              function (acc, cur) {
                  acc[cur[0]] = cur[1];
                  return acc;
              } , {}
          );
          document.getElementById('tw-id').innerHTML = params.user_id;
          document.getElementById('tw-name').innerHTML = params.screen_name;
          model.oauth_token = params.oauth_token;
          model.oauth_token_secret = params.oauth_token_secret;
          model.user_id = params.user_id;
      });
  }

  function sendTweet() {
      const text = document.querySelector("#tweetText").value;
      oauthSend(updateURL, 'post', model.oauth_token_secret, { "status": text }, {
          oauth_token: model.oauth_token,
          oauth_consumer_key: apiKey
      }, function (res) {
          alert("Tweet しました");
      });
  }

  function showMe() {
      oauthSend(usersShowURL, 'get', model.oauth_token_secret, { "user_id": model.user_id }, {
          oauth_token: model.oauth_token,
          oauth_consumer_key: apiKey                    
      }, function (res) {
          const profile = JSON.parse(res.data);
          document.querySelector("#tw-profile").innerHTML = 
              "name: " + profile["name"] + "<br>" +
              "screen_name: " + profile["screen_name"] + "<br>" +
              "location: " + profile["location"] + "<br>" +
              "description: " + profile["description"] + "<br>";
          document.querySelector("#tw-profile-image").src = profile["profile_image_url_https"].replace("_normal", "");
      });
  }
```

ファイルの最初3行で、事前準備で取得した APIキー（コンシューマキー）、APIキーシークレット（コンシューマシクレット）、コールバックURLを設定します。

```javascript
  const apiKey = '【API key（コンシューマキー）】';
  const secretKey = '【API key secret（コンシューマシークレット）】'; 
  const callbackURL = "【登録したコールバックURL（スキーム）】";
```

Connectボタンをタップすると認証画面が表示されます。

ログイン成功後、アクセストークンとユーザーIDを変数 `model` に保持します。 今回のサンプルアプリでは、ログアウト機能はないため、ログイン状態を解除するには、アプリをタスクからkillすることが必要です。

次に `Show Me` ボタンをタップすると、ログインしたユーザー情報と、アイコンが表示されます。 さらに、`Send Tweet` ボタンをタップすると、テキストフォームに入力したメッセージが、Twitterへ投稿されます。

{% hint style="info" %}
Twitterのログイン画面にて、パスワードを「保持します（Remember me）」にチェックした場合、次回からアカウントとパスワードは、入力せずにログイン済みの状態で認可画面になります。Twitterからログアウトしたい場合は、認可画面で右上のアイコンから、サインアウトして下さい。
{% endhint %}

{% hint style="info" %}
このアプリでは、ログイン情報（アクセストークン）を 変数modelに一時的に保持しているため、アプリを再起動するとログイン情報は失われてしまいます。ログインしている状態を維持したい場合は、アクセストークンとユーザーIDをlocalstorage等の永続化可能な場所に保存します。
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ja.docs.monaca.io/sampleapp/samples/twitter_sso.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
