Almost all Internet giants (in a 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 it with just plain core PHP. Yes, actually it is sufficient, lightweight and better.
Application with the OAuth login feature has many advantages.
We have already seen how to integrate Facebook OAuth login into an application. Let us see how to log in 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, and 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 log in. 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 the 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.
The Twitter login authentication flow includes three steps.
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.
Click to see a larger image.
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. This step will change the logged-in status of the application users in the UI.
The process of generating Twitter API keys is straightforward. Once we have seen the steps to get keys for Google OAuth login integration.
Log in to the Twitter developer portal and follow the below steps.
Twitter allows the creation of two types of developer apps. 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 the Config.php file. This application config defines the application constants. It includes the root path, database config and Twitter consumer and secrete keys.
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';
}
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 the “Login with Twitter” integration. It uses no external libraries to achieve this.
It has a custom class that prepares the API request via PHP cURL and handles 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 you 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.
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 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 secret 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 query string.
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;
}
}
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>
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 data.
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, check user existence, and 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;
}
}
The database-related functions are in the DataSource class. It is for creating the database connection and performing read, and 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;
After completing the application config, the home page will display the “Sign in with Twitter” button as below.
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.