I am calling a PHP script with curl API and PHP script return the data in JSON format, I want to use token-based authentication. After a lot of R&D, I found that I can use Firebase JWT authentication.
My curl script is below:
$url = "http://localhost/module/returndata.php";
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL,$url);
$result=curl_exec($ch);
curl_close($ch);
return $result;
and I have a PHP script file(returndata.php) which is return data. Now I want to JWT-Authentication when I called this file. Please suggest me if anyone have an idea regarding it.
Currently, I got some links and created a test file but I don't have an idea how to do it.
<?php
ini_set("display_errors", "1");
error_reporting(E_ALL);
set_time_limit(0);
require_once('vendor/autoload.php');
use \Firebase\JWT\JWT;
define('SECRET_KEY','Admin%^&ttt') ; // secret key can be a random string and keep in secret from anyone
define('ALGORITHM','HS512');
$tokenId = base64_encode(random_bytes(32));
$issuedAt = time();
$notBefore = $issuedAt + 10; //Adding 10 seconds
$expire = $notBefore + 7200; // Adding 60 seconds
$serverName = 'http://localhost/'; /// set your domain name
/*
* Create the token as an array
*/
$data = [
'iat' => $issuedAt, // Issued at: time when the token was generated
'jti' => $tokenId, // Json Token Id: an unique identifier for the token
'iss' => $serverName, // Issuer
'nbf' => $notBefore, // Not before
'exp' => $expire, // Expire
'data' => [ // Data related to the logged user you can set your required data
'id' => "smithjames", // id from the users table
'name' => "admin", // name
]
];
$secretKey = base64_decode(SECRET_KEY);
/// Here we will transform this array into JWT:
$jwt = JWT::encode(
$data, //Data to be encoded in the JWT
$secretKey, // The signing key
ALGORITHM
);
$unencodedArray = ['jwt' => $jwt];
try {
$secretKey = base64_decode(SECRET_KEY);
$DecodedDataArray = JWT::decode($_REQUEST['tokVal'], $secretKey, array(ALGORITHM));
echo "{'status' : 'success' ,'data':".json_encode($DecodedDataArray)." }";die();
} catch (Exception $e) {
echo "{'status' : 'fail' ,'msg':'Unauthorized'}";die();
}
I have already installed "Firebase\JWT\JWT" and working fine but how to implement it.
You need fisrt get login request and take JWT token key to store in cookie or localstorage for next requests with specified header auth. like "Bearer" to confirm stored token is correct or not.
Related
Google docs contains only examples with google/apiclient php library. But the library contains 14888 php files. This is too much for just one google sheet api request with the "Service account key" auth that I need.
Is there an example with a native http google sheet api request for the "Service account key" auth somewhere?
I believe your goal as follows.
You want to retrieve the access token from the service account without using googleapis for PHP.
You want to use Sheets API with the retrieved access token.
In order to achieve your goal, the sample script is as follows.
Sample script:
Before you use this script, please set the variables of $private_key and $client_email using your values of service account. And also, as the sample script for using Sheets API, please set $spreadsheetId and $range. In this case, for example, please share your Google Spreadsheet on your Google Drive with the email of the service account. By this, your Spreadsheet can be seen by the service account. Please be careful this.
<?php
$private_key = "-----BEGIN PRIVATE KEY-----\n###-----END PRIVATE KEY-----\n"; // private_key of JSON file retrieved by creating Service Account
$client_email = "###"; // client_email of JSON file retrieved by creating Service Account
$scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]; // This is a sample scope.
$url = "https://www.googleapis.com/oauth2/v4/token";
$header = array("alg" => "RS256", "typ" => "JWT");
$now = floor(time());
$claim = array(
"iss" => $client_email,
"sub" => $client_email,
"scope" => implode(" ", $scopes),
"aud" => $url,
"exp" => (string)($now + 3600),
"iat" => (string)$now,
);
$signature = base64_encode(json_encode($header, JSON_UNESCAPED_SLASHES)) . "." . base64_encode(json_encode($claim, JSON_UNESCAPED_SLASHES));
$b = "";
openssl_sign($signature, $b, $private_key, "SHA256");
$jwt = $signature . "." . base64_encode($b);
$curl_handle = curl_init();
curl_setopt_array($curl_handle, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => array(
"assertion" => $jwt,
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
),
]);
$res = curl_exec($curl_handle);
curl_close($curl_handle);
$obj = json_decode($res);
$accessToken = $obj -> {'access_token'};
print($accessToken . "\n"); // You can see the retrieved access token.
// The following script is for a sample for using the access token.
$spreadsheetId = '###'; // Please set the Spreadsheet ID.
$range = 'Sheet1';
$curl_test = curl_init();
curl_setopt($curl_test, CURLOPT_URL, 'https://sheets.googleapis.com/v4/spreadsheets/' . $spreadsheetId .'/values/'. $range);
curl_setopt($curl_test, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . $accessToken));
curl_setopt($curl_test, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl_test);
print($response);
When you run this script, the access token is retrieved from the service account, and as a sample, the values of "Sheet1" of $spreadsheetId are retrieved using Sheets API.
References:
Using OAuth 2.0 for Server to Server Applications
jwt.io
Method: spreadsheets.values.get
Although this doesn't answer your question (this might be useful to others who might stumble on this 14888-file issue), please note that you can also reduce the number of classes by specifying which services to use in your composer.json file.
In your case you would need to use the following;
{
"require": {
"google/apiclient": "^2.7"
},
"scripts": {
"post-update-cmd": "Google\\Task\\Composer::cleanup"
},
"extra": {
"google/apiclient-services": [
"Sheets"
]
}
}
Refer to: https://packagist.org/packages/google/apiclient
The access_token acquired by Socialite (via Socialite::driver(self::PROVIDER)->user() has a limited time validity. For Google it's an hour.
I'm able to get refresh_token by changing the redirect call to:
Socialite::driver(self::PROVIDER)->stateless()->with([
'access_type' => 'offline',
])->redirect()
For an hour I'm able to read user data based on access_token by calling
// $token = read_stored_access_token()
\Socialite::driver(self::PROVIDER)->userFromToken($accessToken);
After an hour, when the token gets invalid, the Google API starts returning 401 Unauthorized and Socialite propagates this out:
(1/1) ClientException
Client error: `GET https://www.googleapis.com/plus/v1/people/me?prettyPrint=false` resulted in a `401 Unauthorized` response:
{"error":{"errors":[{"domain":"global","reason":"authError","message":"Invalid Credentials","locationType":"header","loc (truncated...)
Now with the refresh_token, I should be able to easily refresh the access_token. But I cannot find a mention in Socialite docs or source code which would allow me to do that.
Is really the only way how to accomplish this use Google's API library and do this manually? Doesn't it kill the whole idea of using Socialite?
Note: I'm trying to avoid to call redirect() again as it might force user to pick one of his Google accounts every hour which is annoying.
Thanks!
Here is the way I saving user from Socialite with offline access:
$newUser = new User;
$newUser->name = $user->name;
$newUser->email = $user->email;
$newUser->google_id = $user->id;
$newUser->google_token = $user->token;
$newUser->token_expires_at = Carbon::now()->addSeconds($user->expiresIn);
$newUser->google_refresh_token = $user->refreshToken;
$newUser->avatar = $user->avatar;
$newUser->avatar_original = $user->avatar_original;
$newUser->save();
And here is my solution for token refreshing. I made it via creating accessor for token attribute in my User model:
/**
* Accessor for google token of the user
* Need for token refreshing when it has expired
*
* #param $token
*
* #return string
*/
public function getGoogleTokenAttribute( $token ) {
//Checking if the token has expired
if (Carbon::now()->gt(Carbon::parse($this->token_expires_at))) {
$url = "https://www.googleapis.com/oauth2/v4/token";
$data = [
"client_id" => config('services.google.client_id'),
"client_secret" => config('services.google.client_secret'),
"refresh_token" => $this->google_refresh_token,
"grant_type" => 'refresh_token'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$result = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
return $token;
}
$result = json_decode($result, true);
$this->google_token = isset($result['access_token']) ? $result['access_token'] : "need_to_refresh";
$this->token_expires_at = isset($result['expires_in']) ? Carbon::now()->addSeconds($result['expires_in']) : Carbon::now();
$this->save();
return $this->google_token;
}
return $token;
}
return Socialite::driver('google')
->scopes()
->with(["access_type" => "offline", "prompt" => "consent select_account"])
->redirect();
By default refresh_token is returned only on first authorization, by adding "prompt" => "consent select_account" we force it to be returned every time.
I'm currently trying to develop a connection to the Reddit api through Oauth using Guzzle. I get to the point where I authenticate in Reddit, then I get to the authorization token, but I can't take the access token from the Guzzle response so I can set it as a cookie and using on subsequent requests. My current code looks like this:
public function __construct(){
if(isset($_COOKIE['reddit_token'])){
$token_info = explode(":", $_COOKIE['reddit_token']);
$this->token_type = $token_info[0];
$this->access_token = $token_info[1];
} else {
if (isset($_GET['code'])){
//capture code from auth
$code = $_GET["code"];
//construct POST object for access token fetch request
$postvals = sprintf("code=%s&redirect_uri=%s&grant_type=authorization_code",
$code,
redditConfig::$ENDPOINT_OAUTH_REDIRECT);
//get JSON access token object (with refresh_token parameter)
$token = self::runCurl(redditConfig::$ENDPOINT_OAUTH_TOKEN, $postvals, null, true);
//store token and type
if (isset($token->access_token)){
$this->access_token = $token->access_token;
$this->token_type = $token->token_type;
//set token cookie for later use
$cookie_time = 60 * 59 + time(); //seconds * minutes = 59 minutes (token expires in 1hr)
setcookie('reddit_token', "{$this->token_type}:{$this->access_token}", $cookie_time);
}
} else {
$state = rand();
$urlAuth = sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&state=%s",
redditConfig::$ENDPOINT_OAUTH_AUTHORIZE,
redditConfig::$CLIENT_ID,
redditConfig::$ENDPOINT_OAUTH_REDIRECT,
redditConfig::$SCOPES,
$state);
//forward user to PayPal auth page
header("Location: $urlAuth");
}
}
This is my authentication flow. The I have the runCurl method that is going to make the guzzle requests:
private function runCurl($url, $postVals = null, $headers = null, $auth = false){
$options = array(
'timeout' => 10,
'verify' => false,
'headers' => ['User-Agent' => 'testing/1.0']
);
$requestType = 'GET';
if ($postVals != null){
$options['body'] = $postVals;
$requestType = "POST";
}
if ($this->auth_mode == 'oauth'){
$options['headers'] = [
'User-Agent' => 'testing/1.0',
'Authorization' => "{$this->token_type} {$this->access_token}"];
}
if ($auth){
$options['auth'] = [redditConfig::$CLIENT_ID, redditConfig::$CLIENT_SECRET];
}
$client = new \GuzzleHttp\Client();
$response = $client->request($requestType, $url, $options);
$body = $response->getBody();
return $body;
}
The problem resides here, the getBody() method returns a stream, and if I use getBody()->getContents() I get a string, none of which can help me.
Any idea on how can I get the access token so I can finish the authentication process?
To answer the question itself - you just need to cast $body to string. It should be a json, so you will also need to json_decode it to use it as an object in the code above. So instead of return $body; in your runCurl, you need to do:
return json_decode((string)$body);
I would recommend to use the official client tho. The code in the question has some unrelated issues, which will make it costy to maintain.
I'm searching for an hours now and can't find a solution to this problem.
This is the code to generate JWT token. I used https://github.com/firebase/php-jwt library.
$tokenId = base64_encode(mcrypt_create_iv(32));
$issuedAt = time();
$notBefore = $issuedAt + 10; //Adding 10 seconds
$expire = $notBefore + 60; // Adding 60 seconds
$serverName = 'serverName'; // Retrieve the server name from config file
$secretKey = base64_decode(getenv('JWT_SECRET'));
$data = [
'iat' => $issuedAt, // Issued at: time when the token was generated
'jti' => $tokenId, // Json Token Id: an unique identifier for the token
'iss' => $serverName, // Issuer
'nbf' => $notBefore, // Not before
'exp' => $expire, // Expire
'data' => [ // Data related to the signer user
'userId' => '1', // userid from the users table
'userName' => $UserName, // User name
]
];
$jwt = JWT::encode(
$data, //Data to be encoded in the JWT
$secretKey, // The signing key
'HS256' // Algorithm used to sign the token
);
$unencodedArray = ['jwt' => $jwt];
echo json_encode($unencodedArray);
And I verify the token at https://jwt.io/
Can anybody help me with this problem? I'm currently new in JWT. Btw, my project is Slim API.
Thank you very much.
Signature verification fails because you are not passing the correct secret key to https://jwt.io/ You need to pass the value of $secretKey from the PHP code. According to the screenshot you are passing string secret.
I m trying to get access to the linkedin api but like a lot of people, fail everytime and have this following message of error :
missing required parameters, includes an invalid parameter value, parameter more then once. : Unable to retrieve access token : appId or redirect uri does not match authorization code or authorization code expired
I've cheked the hosting server's timestamp and i revoke and create the token on the app admin before i launch the code (the faster i can due to the short life time of the authorization code given).
Here's my index file and just after the functions i use :
<?php
// VARS
define('API_KEY', 'xxxxxxxxxx');
define('API_SECRET', 'xxxxxxxxxx');
define('REDIRECT_URI', 'https://' . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME']);
define('SCOPE', 'r_basicprofile');
session_name('linkedin');
session_start();
include('lib/functions.php');
// OAuth 2 Control Flow
if (isset($_GET['error'])) {
// LinkedIn returned an error
print $_GET['error'] . ': ' . $_GET['error_description'];
exit;
}
elseif (isset($_GET['code'])) {
// User authorized your application
getAccessToken();
}
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
getAuthorizationCode();
}
}
// Congratulations! You have a valid token. Now fetch your profile
$user = fetch('GET', '/v1/people/~:(firstName,lastName)');
print "Hello $user->firstName $user->lastName.";
?>
And the functions :
<?php
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;
}
function getAccessToken() {
$params = array('grant_type' => 'authorization_code',
'client_id' => API_KEY,
'client_secret' => API_SECRET,
'code' => $_GET['code'],
'redirect_uri' => REDIRECT_URI
);
var_dump($params);
// Access Token request
//$url = 'https://www.linkedin.com/uas/oauth2/accessToken?' . http_build_query($params);
$url = 'https://www.linkedin.com/uas/oauth2/accessToken';
$c = curl_init();
curl_setopt($c, CURLOPT_URL, $url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
curl_setopt($c, CURLOPT_HEADER, false);
curl_setopt($c, CURLOPT_POST,true);
curl_setopt($c, CURLOPT_POSTFIELDS, http_build_query($params));
$response = curl_exec($c); // on execute la requete
curl_close($c);
// 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
// DEBUG //
echo 'Retour get access token : </br>';
var_dump($token);
echo '</br>--------------------------</br></br>';
// ! DEBUG //
return true;
}
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
$url = 'https://api.linkedin.com' . $resource;
$c = curl_init();
curl_setopt($c, CURLOPT_URL, $url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
curl_setopt($c, CURLOPT_HEADER, false);
curl_setopt($c, CURLOPT_POSTFIELDS,http_build_query($params));
$response = curl_exec($c);
curl_close($c);
// DEBUG //
echo 'Retour fetch : </br>';
var_dump($response);
echo '</br>--------------------------</br></br>';
// ! DEBUG //
// Native PHP object, please
return json_decode($response);
}
?>
I tried many things but it never works. If someone see the issue tnaks a lot in advance.
Thanks
I have faced the same problem and my issue was caused by http_build_query() function, http://www.php.net/manual/en/function.http-build-query.php
for some reason it tries to encode the parameters to build a query string that will be used by the Linkedin services, if you try to construct the string manually it will pass and it will work.
and actually I reached this page while searching to figure out if its a PHP version issue or maybe the function http_build_query() take in account some environment variables like encoding and locale settings.
not sure but this fixed my issue.