I have created a Twitter application for a website. I would like to integrate the possibility of logging in/registering with Twitter. I am using php files as helpers, which are named TwitterOAuth.php and OAuth.php, respectively. When a user clicks on the Twitter button, he/she is redirected to a page called twitter.php, which has the following source-code:
<?php
/* Build TwitterOAuth object with client credentials. */
$connection = Common::twitter();
/* Get temporary credentials. */
$request_token = $connection->getRequestToken(App::env()->get('url'));
/* Save temporary credentials to session. */
$token = $request_token['oauth_token'];
$_SESSION['twitter'] = array('id' => $request_token['oauth_token'], 'token' => $request_token['oauth_token_secret']);
/* If last connection failed don't display authorization link. */
switch ($connection->http_code) {
case 200:
/* Build authorize URL and redirect user to Twitter. */
$url = $connection->getAuthorizeURL($token);
header('Location: ' . $url);
break;
default:
/* Show notification if something went wrong. */
var_dump($request_token);
echo 'Could not connect to Twitter. Refresh the page or try again later.';
}
The Common::twitter() function is as follows:
/**
* Returns the twitter OAuth service.
*
* #return TwitterOAuth
*/
public static function twitter() {
if (!self::$tw) {
if ((User::isLoggedIn()) && (User::current()->hasTwitterAccount())) {
self::$tw = new TwitterOAuth(
App::env()->get('twitter', 'consumerKey'),
App::env()->get('twitter', 'consumerSecret'),
App::CurrentUser()->getTwitterId(),
App::CurrentUser()->getTwitterUserAccessToken()
);
} else {
self::$tw = new TwitterOAuth(
App::env()->get('twitter', 'consumerKey'),
App::env()->get('twitter', 'consumerSecret')
);
}
}
return self::$tw;
}
In the scenario I am testing with, the else branch is executed. However, I get an exception:
Exception 'PHPErrorException' with message 'Notice [8] Undefined
index: oauth_token Error on line 81 in file ...\lib\TwitterOAuth.php
The function where the problem occurs is as follows:
/**
* Get a request_token from Twitter
*
* #returns a key/value array containing oauth_token and oauth_token_secret
*/
function getRequestToken($oauth_callback) {
$parameters = array();
$parameters['oauth_callback'] = $oauth_callback;
$request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters);
$token = OAuthUtil::parse_parameters($request);
$this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
return $token;
}
The problem is that OAuthUtil::parse_parameters($request) returns an empty array. This is happening, because $request is false, however, $this->requestTokenURL is https://api.twitter.com/oauth/request_token, $parameters has an oauth_callback, which holds the callback URL defined in the Twitter application. What could be the cause of this issue?
EDIT:
Source of `$this->oAuthRequest`:
/**
* Format and sign an OAuth / API request
*/
function oAuthRequest($url, $method, $parameters) {
if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) {
$url = "{$this->host}{$url}.{$this->format}";
}
$request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters);
$request->sign_request($this->sha1_method, $this->consumer, $this->token);
switch ($method) {
case 'GET':
return $this->http($request->to_url(), 'GET');
default:
return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata());
}
}
This method is inside of TwitterOAuth.php.
In OAuth.php there was a code chunk in the get_normalized_http_url method, namely:
$port = #$parts['port'];
This caused some errors, so I have fixed it like this:
$port = (array_key_exists('port', $parts) ? $parts['port'] : 80);
However, apparently port number 80 was the problem and after I changed the chunk to this:
$port = (array_key_exists('port', $parts) ? $parts['port'] : 443);
it worked like a spell.
Related
I am trying to create an auto-login. I don't want my client to login with credentials for this. I am using this library
https://github.com/aweber/public-api-examples/tree/master/php
Created a credentials.ini file like this
clientId = 'xxx'
clientSecret = 'xxx'
accessToken = 'https://auth.aweber.com/oauth2/token'
redirect_uri = 'http://localhost:8080/php-aweber/'
when I try to access the index.php file, It shows an error like this.
//index.php
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
const BASE_URL = 'https://api.aweber.com/1.0/';
// Create a Guzzle client
$client = new GuzzleHttp\Client();
// Load credentials
$credentials = parse_ini_file('credentials.ini');
$accessToken = $credentials['accessToken'];
/**
* Get all of the entries for a collection
*
* #param Client $client HTTP Client used to make a GET request
* #param string $accessToken Access token to pass in as an authorization header
* #param string $url Full URL to make the request
* #return array Every entry in the collection
*/
function getCollection($client, $accessToken, $url) {
$collection = array();
while (isset($url)) {
$request = $client->get($url,
['headers' => ['Authorization' => 'Bearer ' . $accessToken]]
);
$body = $request->getBody();
$page = json_decode($body, true);
$collection = array_merge($page['entries'], $collection);
$url = isset($page['next_collection_link']) ? $page['next_collection_link'] : null;
}
return $collection;
}
// get all of the accounts
$accounts = getCollection($client, $accessToken, BASE_URL . 'accounts');
// get all sharing integration uri's for twitter and facebook
// these are used to create a broadcast that will post to twitter or facebook
// see broadcast example here: https://github.com/aweber/public-api-examples/blob/master/php/create-schedule-broadcast
$integrations = getCollection($client, $accessToken, $accounts[0]['integrations_collection_link']);
echo("Integrations:\n");
foreach ($integrations as $integration) {
if (in_array(strtolower($integration['service_name']), ['twitter', 'facebook'], true)) {
echo "{$integration['service_name']} {$integration['login']} {$integration['self_link']}\n";
}
}
What am I missing or doing wrong?
I installed league/oauth2-client with composer and it created this line in composer.json
"league/oauth2-client": "2.2.0"
When I refreshed get_oauth_token.php page, this error still came out:
Fatal error: Class 'League\OAuth2\Client\Provider\AbstractProvider'
not found in C:\xampp\htdocs...\PHPMailer\get_oauth_token.php on
line 35
Here's get_oauth_token.php
<?php
/**
* Get an OAuth2 token from Google.
* * Install this script on your server so that it's accessible
* as [https/http]://<yourdomain>/<folder>/get_oauth_token.php
* e.g.: http://localhost/phpmail/get_oauth_token.php
* * Ensure dependencies are installed with 'composer install'
* * Set up an app in your Google developer console
* * Set the script address as the app's redirect URL
* If no refresh token is obtained when running this file, revoke access to your app
* using link: https://accounts.google.com/b/0/IssuedAuthSubTokens and run the script again.
* This script requires PHP 5.4 or later
* PHP Version 5.4
*/
namespace League\OAuth2\Client\Provider;
require './vendor/autoload.php';
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
session_start();
//If this automatic URL doesn't work, set it yourself manually
$redirectUri = isset($_SERVER['HTTPS']) ? 'https://' : 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
//$redirectUri = 'http://localhost/phpmailer/get_oauth_token.php';
//These details obtained are by setting up app in Google developer console.
$clientId = 'RANDOMCHARS-----duv1n2.apps.googleusercontent.com';
$clientSecret = 'RANDOMCHARS-----lGyjPcRtvP';
class Google extends AbstractProvider
{
use BearerAuthorizationTrait;
const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'id';
/**
* #var string If set, this will be sent to google as the "access_type" parameter.
* #link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
*/
protected $accessType;
/**
* #var string If set, this will be sent to google as the "hd" parameter.
* #link https://developers.google.com/accounts/docs/OAuth2Login#hd-param
*/
protected $hostedDomain;
/**
* #var string If set, this will be sent to google as the "scope" parameter.
* #link https://developers.google.com/gmail/api/auth/scopes
*/
protected $scope;
public function getBaseAuthorizationUrl()
{
return 'https://accounts.google.com/o/oauth2/auth';
}
public function getBaseAccessTokenUrl(array $params)
{
return 'https://accounts.google.com/o/oauth2/token';
}
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return ' ';
}
protected function getAuthorizationParameters(array $options)
{
if (is_array($this->scope)) {
$separator = $this->getScopeSeparator();
$this->scope = implode($separator, $this->scope);
}
$params = array_merge(
parent::getAuthorizationParameters($options),
array_filter([
'hd' => $this->hostedDomain,
'access_type' => $this->accessType,
'scope' => $this->scope,
// if the user is logged in with more than one account ask which one to use for the login!
'authuser' => '-1'
])
);
return $params;
}
protected function getDefaultScopes()
{
return [
'email',
'openid',
'profile',
];
}
protected function getScopeSeparator()
{
return ' ';
}
protected function checkResponse(ResponseInterface $response, $data)
{
if (!empty($data['error'])) {
$code = 0;
$error = $data['error'];
if (is_array($error)) {
$code = $error['code'];
$error = $error['message'];
}
throw new IdentityProviderException($error, $code, $data);
}
}
protected function createResourceOwner(array $response, AccessToken $token)
{
return new GoogleUser($response);
}
}
//Set Redirect URI in Developer Console as [https/http]://<yourdomain>/<folder>/get_oauth_token.php
$provider = new Google(
array(
'myClientId' => $clientId, //already inserted
'myClientSecret' => $clientSecret, //already inserted
'myRedirectUri' => $redirectUri, //already inserted
'scope' => array('https://mail.google.com/'),
'accessType' => 'offline'
)
);
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->getState();
header('Location: ' . $authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken(
'authorization_code',
array(
'code' => $_GET['code']
)
);
// Use this to get a new access token if the old one expires
echo 'Refresh Token: ' . $token->getRefreshToken();
}
Can you explain me in detail what to do after having installed league/oauth2-client through composer require league/oauth2-client?
Thank you.
Had a similar issue and discovered its not stated explicitly in the official tutorial here.
The library league/oauth2-client requires you to install the provider you need seperately as there are multiple providers both official and third party here.
For google provider you would need
composer require league/oauth2-google
And refer to it like this
use League\OAuth2\Client\Provider\Google;
I have an API built with Slim v2 and I secure certain routes passing a middleware function "authenticate":
/**
* List marca novos
* method GET
* url /novos/marca/:idmarca
*/
$app->get('/novos/marca/:idmarca', 'authenticate', function($idmarca) {
$response = array();
$db = new DbHandler('dbnovos');
// fetching marca
$marca = $db->getMarcaNovos($idmarca);
$response["error"] = false;
$response["marca"] = array();
array_walk_recursive($marca, function(&$val) {
$val = utf8_encode((string)$val);
});
array_push($response["marca"], $marca);
echoRespnse(200, $response, "marcaoutput");
})->via('GET', 'POST');
The authenticate function checks if a headers Authorization value was sent (user_api_key) and checks it against the database.
I'm trying to get the same functionality in a Slim v3 API with the folowwing route:
/**
* List marca novos
* method GET
* url /novos/marca/:idmarca
*/
$app->get('/novos/marca/{idmarca}', function ($request, $response, $args) {
$output = array();
$db = new DbHandler('mysql-localhost');
$marca = $db->getMarcaNovos($args['idmarca']);
if ($marca != NULL) {
$i = 0;
foreach($marca as $m) {
$output[$i]["id"] = $m['id'];
$output[$i]["nome"] = utf8_encode($m['nome']);
$i++;
}
} else {
// unknown error occurred
$output['error'] = true;
$output['message'] = "An error occurred. Please try again";
}
// Render marca view
echoRespnse(200, $response, $output, "marca");
})->add($auth);
This is my middleware
/**
* Adding Middle Layer to authenticate every request
* Checking if the request has valid api key in the 'Authorization' header
*/
$auth = function ($request, $response, $next) {
$headers = $request->getHeaders();
$outcome = array();
// Verifying Authorization Header
if (isset($headers['Authorization'])) {
$db = new DbHandler('mysql-localhost');
// get the api key
$api_key = $headers['Authorization'];
// validating api key
if (!$db->isValidApiKey($api_key)) {
// api key is not present in users table
$outcome["error"] = true;
$outcome["message"] = "Access Denied. Invalid Api key";
echoRespnse(401, $outcome, $output);
} else {
global $user_id;
// get user primary key id
$user_id = $db->getUserId($api_key);
$response = $next($request, $response);
return $response;
}
} else {
// api key is missing in header
$outcome["error"] = true;
$outcome["message"] = "Api key is missing";
//echoRespnse(400, $response, $outcome);
return $response->withStatus(401)->write("Not allowed here - ".$outcome["message"]);
}
};
But I always get the error: "Not allowed here - Api key is missing"
Basically, the test if $headers['Authorization'] is set is failing. What is the $headers array structure or how do I get the Authorization value passed through the header?
If you are sending something else than valid HTTP Basic Authorization header, PHP will not have access to it. You can work around this by adding the following rewrite rule to your .htaccess file.
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Here is a simple query from the linkedIn documentation that works:
$groupData = $this->linkedin->fetch('GET', "/v1/groups/{id}/posts");
It returns 10 records. But the moment I attach the count and start parameters like this:
$groupData = $this->linkedin->fetch('GET', "/v1/groups/{id}/posts?count=20&start=0");
I get this error:
A PHP Error was encountered
Severity: Warning
Message: file_get_contents(https://api.linkedin.com/v1/groups/{id}/posts&count=20&start=0?oauth2_access_token=xxxxx8&format=json): failed to open stream: HTTP request failed! HTTP/1.0 400 Bad Request
Filename: libraries/Linkedin.php
Line Number: 85
Here is my complete code:
class Auth extends CI_Controller {
function __construct() {
parent:: __construct();
$this->load->library('linkedin'); // load library
session_name('linkedin');
session_start();
}
// linkedin login script
function index() {
// OAuth 2 Control Flow
if (isset($_GET['error'])) {
// LinkedIn returned an error
// load any error view here
exit;
} elseif (isset($_GET['code'])) {
// User authorized your application
if ($_SESSION['state'] == $_GET['state']) {
// Get token so you can make API calls
$this->linkedin->getAccessToken();
} else {
// CSRF attack? Or did you mix up your states?
exit;
}
} else {
if ((empty($_SESSION['expires_at'])) || (time() > $_SESSION['expires_at'])) {
// Token has expired, clear the state
$_SESSION = array();
}
if (empty($_SESSION['access_token'])) {
// Start authorization process
$this->linkedin->getAuthorizationCode();
}
}
// this is where I am fetching linkedIn data
$groupData = $this->linkedin->fetch('GET', "/v1/groups/{id}/posts?count=20&start=0");
// this is where I am sending the data to the idea model to be saved
if ($groupData) {
var_dump($groupData); exit();
// foreach ($groupData->values as $data) {
// var_dump($data->creator->firstName); exit();
// }
$this->load->model('idea_model');
$this->idea_model->store_ideas($groupData);
} else {
// linked return an empty array of profile data
}
}
}
The linkedIn library is the code sample given by linkedIn in their documentation:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* CodeIgniter Linked API Class
*
*
* #package CodeIgniter
* #subpackage Libraries
* #category Libraries
* #author Muhamamd Hafeez
*/
class Linkedin {
function __construct(){
}
public function getAuthorizationCode() {
$params = array('response_type' => 'code',
'client_id' => API_KEY,
'scope' => SCOPE,
'state' => uniqid('', true), // unique long string
'redirect_uri' => REDIRECT_URI,
);
// Authentication request
$url = 'https://www.linkedin.com/uas/oauth2/authorization?' . http_build_query($params);
// Needed to identify request when it returns to us
$_SESSION['state'] = $params['state'];
// Redirect user to authenticate
header("Location: $url");
exit;
}
public function getAccessToken() {
$params = array('grant_type' => 'authorization_code',
'client_id' => API_KEY,
'client_secret' => API_SECRET,
'code' => $_GET['code'],
'redirect_uri' => REDIRECT_URI,
);
// Access Token request
$url = 'https://www.linkedin.com/uas/oauth2/accessToken?' . http_build_query($params);
// Tell streams to make a POST request
$context = stream_context_create(
array('http' =>
array('method' => 'POST',
)
)
);
// Retrieve access token information
$response = file_get_contents($url, false, $context);
// Native PHP object, please
$token = json_decode($response);
// Store access token and expiration time
$_SESSION['access_token'] = $token->access_token; // guard this!
$_SESSION['expires_in'] = $token->expires_in; // relative time (in seconds)
$_SESSION['expires_at'] = time() + $_SESSION['expires_in']; // absolute time
return true;
}
public function fetch($method, $resource, $body = '') {
$params = array('oauth2_access_token' => $_SESSION['access_token'],
'format' => 'json',
);
// Need to use HTTPS
$url = 'https://api.linkedin.com' . $resource . '?' . http_build_query($params);
// Tell streams to make a (GET, POST, PUT, or DELETE) request
$context = stream_context_create(
array('http' =>
array('method' => $method,
)
)
);
// Hocus Pocus
$response = file_get_contents($url, false, $context);
// Native PHP object, please
return json_decode($response);
}
}
/* End of file Linked.php */
/* Location: ./application/libraries/linkedin.php */
Please help me fix this. What am I doing wrong?
The problem is that your parameter list has 2 two ?'s. I would change the fetch method to take an optional $params parameter:
public function fetch($method, $resource, $params = array(), $body = '') {
// Cast, just in case
$params = (array)$params;
// Add mandatory parameters
$params['oauth2_access_token'] = $_SESSION['access_token'];
$params['format'] = 'json';
// Need to use HTTPS
$url = 'https://api.linkedin.com' . $resource . '?' . http_build_query($params);
// Tell streams to make a (GET, POST, PUT, or DELETE) request
$context = stream_context_create(
array('http' =>
array('method' => $method,
)
)
);
// Hocus Pocus
$response = file_get_contents($url, false, $context);
// Native PHP object, please
return json_decode($response);
}
And call it like so:
$groupData = $this->linkedin->fetch('GET', "/v1/groups/{id}/posts", array("count" => 20, "start" => 0));
Is it possible to print to my log files the exact request from Facebook PHP SDK to the Facebook Graphs Server?
Can someone explain me how to modify the Facebook PHP Library https://github.com/facebook/php-sdk
I found:
/**
* Invoke the Graph API.
*
* #param String $path the path (required)
* #param String $method the http method (default 'GET')
* #param Array $params the query/post data
* #return the decoded response object
* #throws FacebookApiException
*/
protected function _graph($path, $method = 'GET', $params = array()) {
if (is_array($method) && empty($params)) {
$params = $method;
$method = 'GET';
}
$params['method'] = $method; // method override as we always do a POST
$result = json_decode($this->_oauthRequest(
$this->getUrl('graph', $path),
$params
), true);
// results are returned, errors are thrown
if (is_array($result) && isset($result['error'])) {
$this->throwAPIException($result);
}
return $result;
}
You should rather have a look at the makeRequest function where the actual http request takes place. Since I wouldn't play around in the api, you could also extend the class and override the method:
class FacebookLogger extends Facebook {
protected function makeRequest($url, $params, $ch=null) {
var_dump($url);
var_dump($params);
parent::makeRequest($url, $params, $ch);
}
}