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 plug-in and a third-party advanced-http plug-in, and the signature required for OAuth uses the oauth-signature library.

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

If you want to use third-party Cordova plugins, you need to create a custom build debugger (Android or iOS).

Demo

Import Twitter Single Sign-on App to your Monaca Account

Tested Environment

  • Android 11.0

  • iOS 14.3

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.

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. 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)

  2. 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.)

  3. 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.

  4. 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.

  5. Select the Settings tab and from Authentication, enable 3-legged OAuth.

Import plugin

We will use the InAppBrowser plugin and a third-party advanced-http plugin for single sign-on and use the 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.

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

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

<!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

  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.

  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.

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.

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.

Last updated