Login with Twitter using OAuth1.0a Protocol via API in PHP

Last modified on December 27th, 2020.

Almost all Internet giants (in good sense) like Google, Facebook, Twitter and LinkedIn support OAuth login. They provide API with detailed documentation to help developers integrate OAuth authentication.

There are many client libraries available to implement Twitter OAuth login. But we will do with just plain core PHP. Yes, actually it is sufficient, lightweight and better.

Application with the OAuth login feature has many advantages.

  • Simplifies the login process.
  • Reduces friction by minimising user’s effort with a single click.
  • Saves developers’ effort from building a custom login.
  • Assures secure authentication flow.

We have already seen how to integrate Facebook OAuth login into an application. Let us see how to Login with Twitter using OAuth authentication.

In its community API gallery, Twitter lists many PHP libraries. These libraries contain handlers to read-write API data in a secure manner. An authentication step ensures access security on each API request.

Twitter uses various authentication methods. Those are, OAuth 1.0a, OAuth 2.0 Bearer token, Basic authentication. I used OAuth 1.0a authentication to validate login with Twitter API requests.

During the login flow, Twitter prompts to enter user credentials to login. Then, it will ask to authorize the App for the first time.

“Login with Twitter” flow is very similar to the 3-legged OAuth flow used to get the access token. With the reference of this token, API will return user data as per the request URL. This example will read user name, photo and more details after successful authentication.

In this article, we will see how to integrate “Login with Twitter” by completing each of the below steps.

  • How to get and configure the API keys.
  • How to perform the 3-step authentication flow.
  • Create requests and handle responses during the authentication flow.
  • Store the authenticated user data into the Database.

What is inside?

  1. Twitter OAuth login flow
  2. How to integrate Twitter OAuth login?
  3. Generating Twitter app keys
  4. About this example
  5. Twitter OAuth PHP service
  6. PHP code to handle logged-in user data
  7. Database script
  8. Login with Twitter PHP example output

Twitter OAuth login flow

The Twitter login authentication flow includes three steps.

  1. Get a Request token and a secret-key.
  2. Redirect to Twitter to login and approve access rights to the Twitter app.
  3. Get an Access token and the secret-key to access the user account via API.

During the OAuth login process, each request has to be signed with an OAuth signature. In this example, it has a service class to prepare signed requests.

The following diagram shows the “Login with Twitter” flow. It indicates the steps, request parameters and API response data.

Twitter 3-Step OAuth Login Authentication Diagram

Click to see a larger image.

How to integrate Twitter OAuth login?

Twitter gives a Login with Twitter or Sign in with Twitter button control to put into an application. It makes users sign in to the application with a couple of clicks.

After obtaining the Twitter API keys and token secret, configure them with the PHP application. The next section will show the config file created for this example.

Then, create the request-response handlers to communicate with the Twitter API. It will proceed step by step process to obtain tokens to process the next request.

Instead of using custom handlers, we can use built-in Twitter client libraries.

With the access_token, API will allow access to hit the endpoints. But, it depends on the App permissions set in the developer console.

On getting the response data from the API, the application login flow comes to end. With this step, it will change the logged-in status of the application users in the UI.

Generating Twitter API keys

The process of generating Twitter API keys is straight-forward. Once we have seen the steps to get keys for Google OAuth login integration.

Login to the Twitter developer portal and follow the below steps.

  1. Login to Twitter and go to its developer console.
  2. Create a Twitter developer App. (project-specific app or standalone app).
  3. Go to app settings to edit permissions and authentication settings.
  4. Go to the “keys and tokens” tab to copy the consumer key and the secret key.
  5. Save the keys in a secured place and configure them into the application.

Twitter API Keys

Twitter allows creating two types of developer App. A project-specific app or a standalone app. The project-specific app can use v2 endpoints. The standalone apps can only access the v1 endpoints.

Twitter API keys will no longer keep the API keys and tokens permanently. This is for security purposes. But it allows regenerating the keys and tokens.

Configure the Twitter App consumer_key and secret_key in Config.php file. This application config defines the application constants. It includes the root path, database config and Twitter consumer and secrete key.

Common/config.php

<?php
namespace Phppot;

class Config
{

    const WEB_ROOT = "https://yourdomain/twitter-oauth";

    // Database Configuration
    const DB_HOST = "localhost";

    const DB_USERNAME = "root";

    const DB_PASSWORD = "";

    const DB_NAME = "twitter-oauth";

    // Twitter API configuration
    const TW_CONSUMER_KEY = '';

    const TW_CONSUMER_SECRET = '';

    const TW_CALLBACK_URL = Config::WEB_ROOT . '/signin_with_twitter.php';
}

About this example

There are various ways to implement Twitter OAuth login in a PHP application. Generally, people use built-in client-side libraries to implement this. Twitter also recommends one or more PHP libraries in its community API gallery.

This example shows a simple code for “Login with Twitter” integration. It uses no external libraries to achieve this.

It has a custom class that prepares the API request and handle responses. It creates OAuth signatures to send valid signed requests to the API.

A landing page will show the “Sign in with Twitter” button to trigger the OAuth login process. On clicking, it invokes the PHP service to proceed with the three steps sign-in flow.

As a result, it gets the Twitter user data on successful authentication. The resultant page will change the logged-in state and display the user data.

If you refuse to approve the app access or login, Twitter will redirect back to the application. This redirect URL is set with the param list of the API request.

This example uses the Database to keep the user details read from the API response. Thus, it records the application’s users logged-in via Twitter OAuth login.

Twitter OAuth File Structure

Twitter OAuth PHP service

This PHP service class request Twitter API for the access key and token. It follows the three steps to obtain the access token.

We have seen such similar steps to get the access token in the LinkedIn OAuth login example code earlier.

The following three methods perform the three steps.

Step 1: getRequestToken() – sends the oauth_callback with the authentication header. It requests request_token and the secrete key from the Twitter API.

Step 2: getOAuthVerifier() – redirects the user to the Twitter authentication page. It let users sign in and approve the App to access the account. It passes the OAuth request token received in step1 with the URL. After authentication, Twitter will invoke the oauth_callback with the oauth_verifier in the querystring.

Step 3: getAccessToken() – requests the access_token and secrete key from the API. The params are the request_token, request_token_secret, oauth_verifier get from Step 1, 2.

Twitter requires each of the API requests has to be signed. This PHP service class has a function to generate the signature by the use of API request parameters.

lib/TwitterOAuthLogin.php

<?php
namespace Phppot;

class TwitterOauthService
{

    private $consumerKey;

    private $consumerSecret;

    private $signatureMethod = 'HMAC-SHA1';

    private $oauthVersion = '1.0';

    private $http_status = "";

    public function __construct()
    {
        require_once __DIR__ . '/../Common/Config.php';
        $this->consumerKey = Config::TW_CONSUMER_KEY;
        $this->consumerSecret = Config::TW_CONSUMER_SECRET;
    }

    public function getOauthVerifier()
    {
        $requestResponse = $this->getRequestToken();
        $authUrl = "https://api.twitter.com/oauth/authenticate";
        $redirectUrl = $authUrl . "?oauth_token=" . $requestResponse["request_token"];

        return $redirectUrl;
    }

    public function getRequestToken()
    {
        $url = "https://api.twitter.com/oauth/request_token";

        $params = array(
            'oauth_callback' => Config::TW_CALLBACK_URL,
            "oauth_consumer_key" => $this->consumerKey,
            "oauth_nonce" => $this->getToken(42),
            "oauth_signature_method" => $this->signatureMethod,
            "oauth_timestamp" => time(),
            "oauth_version" => $this->oauthVersion
        );

        $params['oauth_signature'] = $this->createSignature('POST', $url, $params);


        $oauthHeader = $this->generateOauthHeader($params);

        $response = $this->curlHttp('POST', $url, $oauthHeader);

        $responseVariables = array();
        parse_str($response, $responseVariables);

        $tokenResponse = array();

        $tokenResponse["request_token"] = $responseVariables["oauth_token"];
        $tokenResponse["request_token_secret"] = $responseVariables["oauth_token_secret"];

        session_start();
        $_SESSION["oauth_token"] = $tokenResponse["request_token"];
        $_SESSION["oauth_token_secret"] = $tokenResponse["request_token_secret"];
        session_write_close();

        return $tokenResponse;
    }

    public function getAccessToken($oauthVerifier, $oauthToken, $oauthTokenSecret)
    {
        $url = 'https://api.twitter.com/oauth/access_token';

        $oauthPostData = array(
            'oauth_verifier' => $oauthVerifier
        );

        $params = array(
            "oauth_consumer_key" => $this->consumerKey,
            "oauth_nonce" => $this->getToken(42),
            "oauth_signature_method" => $this->signatureMethod,
            "oauth_timestamp" => time(),
            "oauth_token" => $oauthToken,
            "oauth_version" => $this->oauthVersion
        );

        $params['oauth_signature'] = $this->createSignature('POST', $url, $params, $oauthTokenSecret);

        $oauthHeader = $this->generateOauthHeader($params);

        $response = $this->curlHttp('POST', $url, $oauthHeader, $oauthPostData);
        $fp = fopen("eg.log", "a");
        fwrite($fp, "AccessToken: " . $response . "\n");

        $responseVariables = array();
        parse_str($response, $responseVariables);

        $tokenResponse = array();
        $tokenResponse["access_token"] = $responseVariables["oauth_token"];
        $tokenResponse["access_token_secret"] = $responseVariables["oauth_token_secret"];

        return $tokenResponse;
    }

    public function getUserData($oauthVerifier, $oauthToken, $oauthTokenSecret)
    {
        $accessTokenResponse = $this->getAccessToken($oauthVerifier, $oauthToken, $oauthTokenSecret);

        $url = 'https://api.twitter.com/1.1/account/verify_credentials.json';

        $params = array(
            "oauth_consumer_key" => $this->consumerKey,
            "oauth_nonce" => $this->getToken(42),
            "oauth_signature_method" => $this->signatureMethod,
            "oauth_timestamp" => time(),
            "oauth_token" => $accessTokenResponse["access_token"],
            "oauth_version" => $this->oauthVersion
        );

        $params['oauth_signature'] = $this->createSignature('GET', $url, $params, $accessTokenResponse["access_token_secret"]);

        $oauthHeader = $this->generateOauthHeader($params);

        $response = $this->curlHttp('GET', $url, $oauthHeader);

        return $response;
    }

    public function curlHttp($httpRequestMethod, $url, $oauthHeader, $post_data = null)
    {

        $ch = curl_init();
        $fp = fopen("eg.log", "a");
        fwrite($fp, "Header: " . $oauthHeader . "\n");

        $headers = array(
            "Authorization: OAuth " . $oauthHeader
        );

        $options = [
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_HEADER => false,
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
        ];
        if($httpRequestMethod == 'POST') {
            $options[CURLOPT_POST] = true;
        }
        if(!empty($post_data)) {
            $options[CURLOPT_POSTFIELDS] = $post_data;
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);

        $this->http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return $response;
    }

    public function generateOauthHeader($params)
    {
        foreach ($params as $k => $v) {

            $oauthParamArray[] = $k . '="' . rawurlencode($v) . '"';
        }
        $oauthHeader = implode(', ', $oauthParamArray);

        return $oauthHeader;
    }

    public function createSignature($httpRequestMethod, $url, $params, $tokenSecret = '')
    {
        $strParams = rawurlencode(http_build_query($params));

        $baseString = $httpRequestMethod . "&" . rawurlencode($url) . "&" . $strParams;

        $fp = fopen("eg.log", "a");
        fwrite($fp, "Baaaase: " . $baseString . "\n");

        $signKey = $this->generateSignatureKey($tokenSecret);
        $oauthSignature = base64_encode(hash_hmac('sha1', $baseString, $signKey, true));

        return $oauthSignature;
    }

    public function generateSignatureKey($tokenSecret)
    {
        $signKey = rawurlencode($this->consumerSecret) . "&";
        if (! empty($tokenSecret)) {
            $signKey = $signKey . rawurlencode($tokenSecret);
        }
        return $signKey;
    }

    public function getToken($length)
    {
        $token = "";
        $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        $codeAlphabet .= "abcdefghijklmnopqrstuvwxyz";
        $codeAlphabet .= "0123456789";
        $max = strlen($codeAlphabet) - 1;
        for ($i = 0; $i < $length; $i ++) {
            $token .= $codeAlphabet[$this->cryptoRandSecure(0, $max)];
        }
        return $token;
    }

    public function cryptoRandSecure($min, $max)
    {
        $range = $max - $min;
        if ($range < 1) {
            return $min; // not so random...
        }
        $log = ceil(log($range, 2));
        $bytes = (int) ($log / 8) + 1; // length in bytes
        $bits = (int) $log + 1; // length in bits
        $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
            $rnd = $rnd & $filter; // discard irrelevant bits
        } while ($rnd >= $range);
        return $min + $rnd;
    }
}

Initiate login flow with “Sign in with Twitter” control

The landing page of this example will show a “Sign in with Twitter” button. On clicking this button, it invokes functions to proceed with the 3-step login flow.

The following code shows the index.php file script. It checks if any user logged-in already. If so, it displays the user dashboard. Otherwise, it shows the “Sign in with Twitter” button.

It invokes the TwitterOAuthService to initiate the login flow. This initiation will happen when the user tries to log in.

index.php

<?php
namespace Phppot;

if (isset($_GET["action"]) && $_GET["action"] == "login") {
    require_once __DIR__ . '/lib/TwitterOauthService.php';
    $twitterOauthService = new TwitterOauthService();
    $redirectUrl = $twitterOauthService->getOauthVerifier();
    header("Location: " . $redirectUrl);
    exit();
}

session_start();
if ($_SESSION["id"]) {
    $memberId = $_SESSION["id"];
}
session_write_close();

?>
<html>
<head>
<title>Home</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
    <div class="phppot-container">
<?php
if (empty($memberId)) {
    ?>
    <a href="?action=login"> <img class="twitter-btn"
            src="sign-in-with-twitter.png"></a>
<?php
} else {
    require_once './lib/Member.php';
    $member = new Member();
    $userData = $member->getUserById($memberId);
    ?>
<div class="welcome-messge-container">
            <img src="<?php echo $userData[0]["photo_url"]; ?>"
                class="profile-photo" />
            <div>Welcome <?php echo $userData[0]["screen_name"]; ?></div>
        </div>
<?php
}
?>
</div>
</body>
</html>

PHP code to handle logged-in user data

After completing the 3-steps, the TwitterOauthService will return the user access token. Then it invokes GET oauth/verify_credentials endpoint to read the user daya.

It will return the logged-in user data as a JSON response. The application callback endpoint receives this data.

Then, the code will save the data into the database and put the logged-in user id into the session. Based on the existence of this user session the landing page will show the user dashboard.

sign-in-with-twitter.php

<?php
namespace Phppot;

require_once './lib/TwitterOauthService.php';
$TwitterOauthService = new TwitterOauthService();

session_start();
$oauthTokenSecret = $_SESSION["oauth_token_secret"];

if (! empty($_GET["oauth_verifier"]) && ! empty($_GET["oauth_token"])) {
    $userData = $TwitterOauthService->getUserData($_GET["oauth_verifier"], $_GET["oauth_token"], $oauthTokenSecret);
    $userData = json_decode($userData, true);
    if (! empty($userData)) {
        $oauthId = $userData["id"];
        $fullName = $userData["name"];
        $screenName = $userData["screen_name"];
        $photoUrl = $userData["profile_image_url"];

        require_once './lib/Member.php';
        $member = new Member();
        $isMemberExists = $member->isExists($oauthId);
        if (empty($isMemberExists)) {
            $memberId = $member->insertMember($oauthId, $fullName, $screenName, $photoUrl);
        } else {
            $memberId = $isMemberExists[0]["id"];
        }
        if (! empty($memberId)) {
            unset($_SESSION["oauth_token"]);
            unset($_SESSION["oauth_token_secret"]);
            $_SESSION["id"] = $memberId;
            header("Location: index.php");
        }
    }
} else {
    ?>
<HTML>
<head>
<title>Signin with Twitter</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
    <div class="phppot-container">
        <div class="error">
            Sorry. Something went wrong. <a href="index.php">Try again</a>.
        </div>
    </div>
</body>
</HTML>
<?php
}
session_write_close();
exit();

The following PHP class has functions to prepare database queries. It is to read data, to check user existency, to insert new records.

lib/Member.php

<?php
namespace Phppot;

class Member
{

    private $db;

    private $userTbl;

    function __construct()
    {
        require_once __DIR__ . '/DataSource.php';
        $this->db = new DataSource();
    }

    function isExists($twitterOauthId)
    {
        $query = "SELECT * FROM tbl_member WHERE oauth_id = ?";
        $paramType = "s";
        $paramArray = array(
            $twitterOauthId
        );
        $result = $this->db->select($query, $paramType, $paramArray);
        return $result;
    }

    function insertMember($oauthId, $fullName, $screenName, $photoUrl)
    {
        $query = "INSERT INTO tbl_member (oauth_id, oauth_provider, full_name, screen_name, photo_url) values (?,?,?,?,?)";
        $paramType = "sssss";
        $paramArray = array(
            $oauthId,
            'twitter',
            $fullName,
            $screenName,
            $photoUrl
        );
        $this->db->insert($query, $paramType, $paramArray);
    }

    function getUserById($id)
    {
        $query = "SELECT * FROM tbl_member WHERE id = ?";
        $paramType = "i";
        $paramArray = array(
            $id
        );
        $result = $this->db->select($query, $paramType, $paramArray);
        return $result;
    }
}

DataSource class and Database script

The database related functions are in the DataSource class. It is for creating the database connection and to perform read, write operations.

It uses MySQLi prepared statements to execute database queries. It will help to have a secured code that prevents SQL injection.

lib/DataSource.php

<?php
/**
 * Copyright (C) Phppot
 *
 * Distributed under 'The MIT License (MIT)'
 * In essense, you can do commercial use, modify, distribute and private use.
 * Though not mandatory, you are requested to attribute Phppot URL in your code or website.
 */
namespace Phppot;

/**
 * Generic datasource class for handling DB operations.
 * Uses MySqli and PreparedStatements.
 *
 * @version 2.6 - recordCount function added
 */
class DataSource
{

    const HOST = 'localhost';

    const USERNAME = 'root';

    const PASSWORD = 'test';

    const DATABASENAME = 'oauth_login';

    private $conn;

    /**
     * PHP implicitly takes care of cleanup for default connection types.
     * So no need to worry about closing the connection.
     *
     * Singletons not required in PHP as there is no
     * concept of shared memory.
     * Every object lives only for a request.
     *
     * Keeping things simple and that works!
     */
    function __construct()
    {
        $this->conn = $this->getConnection();
    }

    /**
     * If connection object is needed use this method and get access to it.
     * Otherwise, use the below methods for insert / update / etc.
     *
     * @return \mysqli
     */
    public function getConnection()
    {
        $conn = new \mysqli(self::HOST, self::USERNAME, self::PASSWORD, self::DATABASENAME);

        if (mysqli_connect_errno()) {
            trigger_error("Problem with connecting to database.");
        }

        $conn->set_charset("utf8");
        return $conn;
    }

    /**
     * To get database results
     *
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     * @return array
     */
    public function select($query, $paramType = "", $paramArray = array())
    {
        $stmt = $this->conn->prepare($query);

        if (! empty($paramType) && ! empty($paramArray)) {

            $this->bindQueryParams($stmt, $paramType, $paramArray);
        }
        $stmt->execute();
        $result = $stmt->get_result();

        if ($result->num_rows > 0) {
            while ($row = $result->fetch_assoc()) {
                $resultset[] = $row;
            }
        }

        if (! empty($resultset)) {
            return $resultset;
        }
    }

    /**
     * To insert
     *
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     * @return int
     */
    public function insert($query, $paramType, $paramArray)
    {
        $stmt = $this->conn->prepare($query);
        $this->bindQueryParams($stmt, $paramType, $paramArray);

        $stmt->execute();
        $insertId = $stmt->insert_id;
        return $insertId;
    }

    /**
     * To execute query
     *
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     */
    public function execute($query, $paramType = "", $paramArray = array())
    {
        $stmt = $this->conn->prepare($query);

        if (! empty($paramType) && ! empty($paramArray)) {
            $this->bindQueryParams($stmt, $paramType, $paramArray);
        }
        $stmt->execute();
    }

    /**
     * 1.
     * Prepares parameter binding
     * 2. Bind prameters to the sql statement
     *
     * @param string $stmt
     * @param string $paramType
     * @param array $paramArray
     */
    public function bindQueryParams($stmt, $paramType, $paramArray = array())
    {
        $paramValueReference[] = &$paramType;
        for ($i = 0; $i < count($paramArray); $i ++) {
            $paramValueReference[] = &$paramArray[$i];
        }
        call_user_func_array(array(
            $stmt,
            'bind_param'
        ), $paramValueReference);
    }

    /**
     * To get database results
     *
     * @param string $query
     * @param string $paramType
     * @param array $paramArray
     * @return array
     */
    public function getRecordCount($query, $paramType = "", $paramArray = array())
    {
        $stmt = $this->conn->prepare($query);
        if (! empty($paramType) && ! empty($paramArray)) {

            $this->bindQueryParams($stmt, $paramType, $paramArray);
        }
        $stmt->execute();
        $stmt->store_result();
        $recordCount = $stmt->num_rows;

        return $recordCount;
    }
}

The below section shows the tbl_member database table script. Import this script before executing this example.

sql/structure.sql

--
-- Database: `oauth_login`
--

-- --------------------------------------------------------

--
-- Table structure for table `tbl_member`
--

CREATE TABLE `tbl_member` (
  `id` int(11) NOT NULL,
  `oauth_id` varchar(255) NOT NULL,
  `oauth_provider` varchar(255) NOT NULL,
  `full_name` varchar(255) NOT NULL,
  `screen_name` varchar(255) NOT NULL,
  `photo_url` varchar(255) NOT NULL,
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Indexes for dumped tables
--

--
-- Indexes for table `tbl_member`
--
ALTER TABLE `tbl_member`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `tbl_member`
--
ALTER TABLE `tbl_member`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

Login with Twitter example output

After completing the application config, the home page will display the “Sign in with Twitter” button as below.

Sign In with Twitter Gray

Before login, the home page will display the “Sign in with Twitter” button as below. I used the login button downloaded from the official Twitter documentation.

User Dashboard Twitter Login

Download

Leave a Reply

Your email address will not be published. Required fields are marked *

↑ Back to Top