# Twitter Single Sign-on App

Here, we will explain the method of single sign-on (SSO) using OAuth on Twitter.

For single sign-on, use the [InAppBrowser](https://docs.monaca.io/ja/reference/cordova_10.0/inappbrowser/) plug-in and a third-party [advanced-http](https://www.npmjs.com/package/cordova-plugin-advanced-http) plug-in, and the signature required for OAuth uses the [oauth-signature](https://www.npmjs.com/package/oauth-signature) library.

After successful authentication, you can display the user's basic information on the app and post tweets.

{% hint style="info" %}
If you want to use third-party Cordova plugins, you need to create a custom build debugger (Android or iOS).
{% endhint %}

## Demo

[Import Twitter Single Sign-on App to your Monaca Account](https://monaca.mobi/directimport?pid=5ff83196e78885a728ca7a23)

**Tested Environment**

* Android 11.0
* iOS 14.3

![](https://3091308003-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MfWe1U2tFctp8FkP9W8%2F-MfajZI-CU9Oiq6FhCqs%2F-MfakgLoofYZ_vEn4Qk9%2Fimage.png?alt=media\&token=42d78be9-8cb4-408a-a48b-13042e77bab3)

## Prerequisite

### Getting Twitter Consumer Key and Consumer Secret

To use the Twitter API, you need to register a developer account. Register a developer account from [Twitter Developer Account](https://developer.twitter.com/en/apply-for-access).

Next, on the Twitter Developer Portal page, register your app and issue a Consumer Key (API key), Consumer Secret (API secret key), and Bearer token.

1. Go to [the overview page of the Twitter Developer Portal](https://developer.twitter.com/en/portal/projects-and-apps).
2. Create Standalone Apps from the `Create App` button at the bottom of the screen. (Instead of creating Standalone Apps, you can create Apps under Projects)
3. Enter the Name (app name), Description (app description), and Website (URL from which the app will be downloaded). (\* You cannot use a name that has already been used. There is a limit to the number of apps that can be created per day.)
4. Enter the Callback URL (optional: the page that will be displayed after successful authentication). In this sample app, set `mymonacaapp://`. Please change to the one for your app later. This Callback URL is also required when implementing the app.
5. Select the `Settings` tab and from `App permissions`, grant `Read and Write` permissions. If you do not tweet from the app, only allow `Read` permission.
6. Select the `Settings` tab and from `Authentication`, enable `3-legged OAuth`.

## Import plugin

We will use the [InAppBrowser](https://docs.monaca.io/ja/reference/cordova_10.0/inappbrowser/) plugin and a third-party [advanced-http](https://www.npmjs.com/package/cordova-plugin-advanced-http) plugin for single sign-on and use the [oauth-signature](https://www.npmjs.com/package/oauth-signature) library to create the signatures needed for OAuth.

1. From the Monaca Cloud IDE, select `Settings → Manage Cordova Plugins`.
2. Enable the `InAppBrowser` plugin.

![](https://3091308003-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MfWe1U2tFctp8FkP9W8%2F-MfajZI-CU9Oiq6FhCqs%2F-MfakupaZsd08ML7udW3%2Fimage.png?alt=media\&token=96bc5cb6-01f4-487e-8073-077da77b7621)

&#x20;   3\. Click the button "Import Cordova Plugin", check "Specify URL or package name", enter `cordova-plugin-advanced-http` for the package name, and click the OK button.

## Import JS components

1. From Monaca Cloud IDE, select `Settings → Add / Remove JS / CSS Components`.
2. Search by typing `oauth-signature-js` in the component search form. Install `oauth-signature-js` from the search results. After that, select `components/oauth-signature-js/dist/oauth-signature.js` in the selection of the load target and save it. Please note that the `oauth-signature.js` library is BSD-3-Clause, so please be careful when distributing the source code and binary code of the application.

## App description

### File organization

![](https://3091308003-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MfWe1U2tFctp8FkP9W8%2F-MfajZI-CU9Oiq6FhCqs%2F-Mfal2xRYvdq62iLE9tN%2Fimage.png?alt=media\&token=ec6349f8-51c5-42de-8e13-142f1191182a)

| File            | Description                                                                       |
| --------------- | --------------------------------------------------------------------------------- |
| `index.html`    | App screen page                                                                   |
| `css/style.css` | App stylesheet                                                                    |
| `js/app.js`     | JavaScript file that performs various processing when the application is executed |

### HTML description

#### 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>
```

This page is the application screen.

This page is roughly divided into 3 blocks. Each block is separated by `<hr>` tags

1. Login block:

   There is a button to move to the Twitter login screen and a component that displays the user ID and user name (screen name) after login.
2. Profile block:

   There is a button to get the profile of the logged-in user and a component to display it after a successful acquisition.
3. Tweet block:

   There is a text area and a button to tweet.

### JavaScript description

#### app.js

```javascript
  const apiKey = '[Consumer Key（API key）]';
  const secretKey = '[Consumer Secret （API key secret）]'; 
  const callbackURL = '[Registered callback URL (scheme)]';

  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", "");
      });
  }
```

In the first three lines of the file, set the API key (consumer key), API key secret (consumer secret), and callback URL you obtained from the Twitter Developer account.

```javascript
  const apiKey = '[Consumer Key（API key）]';
  const secretKey = '[Consumer Secret （API key secret）]'; 
  const callbackURL = '[Registered callback URL (scheme)]';
```

Tap the Connect button to display the authentication screen.

After a successful login, the access token and user ID are stored in the variable `model`. In this sample app, there is no log-out function, so you need to kill the app from the task to cancel the login status.

Then tap the `Show Me` button to see the logged-in user information and the icon. Furthermore, when you tap the `Send Tweet` button, the message entered in the text form will be posted to Twitter.

{% hint style="info" %}
If you check "Remember me" on the Twitter login screen, the authorization screen will be displayed in the logged-in state without entering the account and password from the next time. If you want to log out from Twitter, please sign out from the icon on the upper right of the authorization screen.
{% endhint %}

{% hint style="info" %}
In this app, the login information (access token) is temporarily stored in the variable model, so the login information will be lost when the app is restarted. If you want to stay logged in, save your access token and user ID in a persistent location such as localstorage.
{% endhint %}
