Google always returns false verifying id token - php

I have the next code, got directly from google reference (https://developers.google.com/identity/sign-in/web/backend-auth)
public function verifyFromAndroid($idToken=null) {
if(empty($idToken)) {
$idToken = self::SAMPLE_ID_TOKEN;
}
$client = new Google_Client(['client_id' => self::CLIENT_ID]);
$payload = $client->verifyIdToken($idToken);
if ($payload) {
print_r($payload);
$userid = $payload['sub'];
// If request specified a G Suite domain:
//$domain = $payload['hd'];
} else {
var_dump($payload);
$this->lastError = "Invalid ID token";
return false;
}
}
But this method always returns false, even using a valid id token that is created and working using the oauthplayground online tool.
The next code works fine, using directly the GoogleAccessToken_Verify class. Can someone tell me why the official Google code doesn't work and yes my own code using the official Google-clien-php sdk?
try {
$verify = new Google_AccessToken_Verify();
$result = $verify->verifyIdToken($this->idToken);
if($result) {
print_r($result);
$friendlyData = $this->translateData($result, true);
if(!$friendlyData) {
return false;
}
return $friendlyData;
}
else {
$this->lastError = "Invalid token verification, no error code";
return false;
}
}
catch(UnexpectedValueException $ex) {
$this->lastError = "UnVaEx (Code {$ex->getCode()}): {$ex->getMessage()}";
return false;
}

try adding complete client ID
xxxxxxxxxxxxxx-xxxxx-yy-zz.apps.googleusercontent.com
while initiating the
$client = new Google_Client(['client_id' => self::CLIENT_ID]);
It should work i was also facing the same issue ...

Had a similar issue.Deleted my android app on firebase console and created a fresh app wirh debug key sha1.Then downloaded and replaced my google.json into my app.This fixed my issue.This has happened to me twice now. At times you just need to recreate the android app on firebase console.

Before you begin register your backend URL at https://developers.google.com/identity/sign-in/web/sign-in with Configure your project button and don't use any credidentials or api key in your code. After doing them your code should look like to this.
public function verifyFromAndroid($idToken=null) {
if(empty($idToken)) {
$idToken = self::SAMPLE_ID_TOKEN;
}
//As you notice we don't use any key as a parameters in Google_Client() API function
$client = new Google_Client();
$payload = $client->verifyIdToken($idToken);
if ($payload) {
print_r($payload);
$userid = $payload['sub'];
// If request specified a G Suite domain:
//$domain = $payload['hd'];
} else {
var_dump($payload);
$this->lastError = "Invalid ID token";
return false;
}
}
I hope it helps.

I faced the same issue. After checking different PHP versions, I found that the google client library is working in PHP7.4 but not with PHP8.0.
Please try the below code after downgrading the version of PHP to 7.4
require_once 'vendor/autoload.php';
$id_token = $_POST['credential'];
$client = new Google_Client(['client_id' => $CLIENT_ID]); // Specify the CLIENT_ID of the app that accesses the backend
$payload = $client->verifyIdToken($id_token);
if ($payload) {
$userid = $payload['sub'];
// If request specified a G Suite domain:
//$domain = $payload['hd'];
} else {
// Invalid ID token
}
Or For development and debugging, you can call google oauth2 tokeninfo validation endpoint.
https://oauth2.googleapis.com/tokeninfo?id_token=$id_token

Related

How do I authenticate a service account in Google Cloud Services in PHP?

While developing Recaptcha Enterprise for use of the V2 "I am not a robot" checkbox, I am stuck on this error:
Fatal error: Uncaught DomainException: Could not load the default credentials. Browse to https://developers.google.com/accounts/docs/application-default-credentials for more information
I follow the link and have settled on this to authenticate:
use Google\Cloud\Storage\StorageClient;
$storage = new StorageClient([
'keyFile' => json_decode(file_get_contents($path_to_keyfile), true),
'projectId' => 'MY_PROJECT'
]);
I cannot find anything else that suggests I need to do anything more than this, and this link to the constructor API doesn't suggest I can pass it in as a parameter and then proceed. I do not want to use environment variables for this project, I want to connect manually in the code. What am I missing? I can confirm I have a working service account.
If it's helpful, the code I'm trying to run after I presumably authenticate is this:
// ==================== CAPTCHA ===================
use Google\Cloud\RecaptchaEnterprise\V1\RecaptchaEnterpriseServiceClient;
use Google\Cloud\RecaptchaEnterprise\V1\Event;
use Google\Cloud\RecaptchaEnterprise\V1\Assessment;
use Google\Cloud\RecaptchaEnterprise\V1\TokenProperties\InvalidReason;
$captcha_response = $_POST['g-recaptcha-response'];
$site_key = "123456789abc";
$client = new RecaptchaEnterpriseServiceClient();
define('SITE_KEY', $site_key);
define('TOKEN', $captcha_response);
define('PROTECTED_ACTION', 'signup');
define('PARENT_PROJECT', 'projects/MY_PROJECT');
$event = (new Event())
->setSiteKey(SITE_KEY)
->setExpectedAction(PROTECTED_ACTION)
->setToken(TOKEN);
$assessment = (new Assessment())
->setEvent($event);
try {
$response = $client->createAssessment(
PARENT_PROJECT,
$assessment
);
if ($response->getTokenProperties()->getValid() == false) {
printf('The CreateAssessment() call failed because the token was invalid for the following reason: ');
printf(InvalidReason::name($response->getTokenProperties()->getInvalidReason()));
} else {
if ($response->getEvent()->getExpectedAction() == PROTECTED_ACTION) {
printf('The score for the protection action is:');
printf($response->getRiskAnalysis()->getScore());
}
else
{
printf('The action attribute in your reCAPTCHA tag does not match the action you are expecting to score');
}
}
} catch (exception $e) {
printf('CreateAssessment() call failed with the following error: ');
printf($e);
}
Here's how I got it working. Thanks to John Hanley for the help in a previous answer. The documentation had lead me to believe that (for whatever reason) Storage was required, but that was not the case: it was as simple as providing the path to the key via the credentials parameter. Not the keyFile parameter.
if (empty($_POST['g-recaptcha-response']))
die("You have failed the not-a-robot check.");
$captcha_response = $_POST['g-recaptcha-response'];
require 'composer/vendor/autoload.php';
use Google\Cloud\RecaptchaEnterprise\V1\RecaptchaEnterpriseServiceClient;
use Google\Cloud\RecaptchaEnterprise\V1\Event;
use Google\Cloud\RecaptchaEnterprise\V1\Assessment;
use Google\Cloud\RecaptchaEnterprise\V1\TokenProperties\InvalidReason;
$path_to_keyfile = "MY_PROJECT-1234567890abc.json";
$site_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
$client = new RecaptchaEnterpriseServiceClient([
'credentials' => $path_to_keyfile,
'projectId' => 'MY_PROJECT'
]);
define('SITE_KEY', $site_key);
define('TOKEN', $captcha_response);
define('PROTECTED_ACTION', 'signup');
define('PARENT_PROJECT', 'projects/MY_PROJECT');
$event = (new Event())
->setSiteKey(SITE_KEY)
->setExpectedAction(PROTECTED_ACTION)
->setToken(TOKEN);
$assessment = (new Assessment())
->setEvent($event);
try {
$response = $client->createAssessment(PARENT_PROJECT, $assessment);
if ($response->getTokenProperties()->getValid() == false) {
printf('The CreateAssessment() call failed because the token was invalid for the following reason: ');
printf(InvalidReason::name($response->getTokenProperties()->getInvalidReason()));
exit;
} else {
if ($response->getEvent()->getExpectedAction() == PROTECTED_ACTION) {
// Closer to 1 = human, to 0 = robot.
$bot_score = $response->getRiskAnalysis()->getScore();
// do what you want with the score here...
} else {
die('The action attribute in your reCAPTCHA tag does not match the action you are expecting to score');
}
}
} catch (exception $e) {
printf('CreateAssessment() call failed with the following error: ');
printf($e);
exit;
}
Your problem is that you are not specifying the service account to use in the client constructor and the system is falling back to using ADC (Application Default Credentials).
ADC will check the environment variable GOOGLE_APPLICATION_CREDENTIALS for the service account JSON key file.
You can set the environment variable before running your program:
Windows:
set GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
Linux:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
Or modify your program by changing this line of code:
$client = new RecaptchaEnterpriseServiceClient();
To this:
$options = ['keyFile' => $path_to_keyfile];
$client = new RecaptchaEnterpriseServiceClient($options);
Note 1:
If you are running your program on a Google Cloud computer service such as Compute Engine, App Engine, Cloud Run, ... the default service account will be used if neither of the above methods are implemented.
Note 2:
While developing, another method is to use the CLI's application default credentials. Run the following command using the Google Cloud SDK CLI:
gcloud auth application-default login
However, I have not verified that the reCAPTCHA Enterprise library checks for this type of credential.

Cannot use object of type Google_Auth_LoginTicket as array

I was performing Google Sign in on Android Application for the first time. At the client side, I obtained the access token and sent to the PHP server via POST.
By referring to Google's Documentation, the code I used in backend is as follows:
$id_token = $_POST['id_token'];
$CLIENT_ID = "** MY WEB APPLICATION CLIENT ID **";
$client = new Google_Client(['client_id' => $CLIENT_ID]);
$client->setAuthConfigFile('client_secret.json');
...
$payload = $client->verifyIdToken($id_token);
if ($payload) {
$userid = $payload['sub'];
} else {
echo "Invalid Token";
}
When obtaining user id, the error is : "Cannot use object of type Google_Auth_LoginTicket as array"
I am pretty new to Google sign in. Please point out what all has gone wrong.
Found It. I made some modifications to the code
$token_data = $client->verifyIdToken($id_token)->getAttributes();
$user_id = $token_data['payload']['sub'];
Now the user id is retrieved fine.

Class 'Silex\Application' not found

I’ve done with google sign-up, I want to ask regarding google token_id authentication. Google issues a token-id to every user which changes on every sign-in, I am getting that token-id when the user sign-in, I want to authenticate that token-id from google to verify if the sign-in was original or fake. I am using this php api provided by google, but it is continuously giving this error:
Uncaught Error: Class 'Silex\Application' not found in C:\xampp\htdocs\final\gplus-verifytoken-php-master\verify.php:23
Stack trace: #0 {main} thrown in C:\xampp\htdocs\final\gplus-verifytoken-php-master\verify.php on line 23
require_once __DIR__.'/vendor/autoload.php';
require_once __DIR__.'/google-api-php-client/src/Google_Client.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
const CLIENT_ID = 'xyz';
const CLIENT_SECRET = 'xyz';
const APPLICATION_NAME = "xyz";
$client = new Google_Client();
$client->setApplicationName(APPLICATION_NAME);
$client->setClientId(CLIENT_ID);
$client->setClientSecret(CLIENT_SECRET);
$app = new Silex\Application();
$app['debug'] = true;
$app->register(new Silex\Provider\TwigServiceProvider(), array(
'twig.path' => __DIR__,
));
$app->register(new Silex\Provider\SessionServiceProvider());
// Initialize a session for the current user, and render index.html.
$app->get('/', function () use ($app) {
return $app['twig']->render('index.html', array(
'CLIENT_ID' => CLIENT_ID,
'APPLICATION_NAME' => APPLICATION_NAME
));
});
// Verify an ID Token or an Access Token.
// Example URI: /verify?id_token=...&access_token=...
$app->post('/verify', function (Request $request) use($app, $client) {
$id_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImE0MzY0YjVmYjliODYxYzNhYTRkYTg5NWExMjk5NzZjMjgyZGJmYzIifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiaWF0IjoxNDg1NDEyMjQ1LCJleHAiOjE0ODU0MTU4NDUsImF0X2hhc2giOiJMSV9DTWxzeG1lSTdvQm9lSUxoSjZRIiwiYXVkIjoiNDY4MzU1OTM0NzMzLXZqNnRkdDJtazEwZ3R0OHJvZGY2bG84MHM4czdtdTRrLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyNjE1NTE5MDY0MTc3ODI0NTgzIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjQ2ODM1NTkzNDczMy12ajZ0ZHQybWsxMGd0dDhyb2RmNmxvODBzOHM3bXU0ay5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoibWdoYXphbmZhcmFsaWtoYW4wOUBnbWFpbC5jb20ifQ.Bpa2_zeVebQ7xtKXvuEell50bvUtKOGb5ZertUZGvzGWXnlA-c2kw4Mvko9Xd4JI_R4wbFoyBtrGCiK0jAlJMgaIH8p3wJbzNKPZ-gPFJdX8mv4v42v8-9urGM7rRUCDylz16WEcR1A2qOmEcNCpCf0_FGNpChl8sc8q8zvTnIb_zYYHp_V7ebR2RlUuO2z9G5YzBN3hZDnmen1xLStmNmYKsIiP5ypMqbWaLjnXJjre6bjTuIGymg_phDYDmwWMVTJyx88zmKAfwQTCh2u3qe_fkCDxxm0MO2wC29__q4uc0BfUNdH62GOrNTBJXmPTUZuT1vdUhzz4CLu1KUohWg";
/*$id_token = $request->get("id_token");*/
$access_token = $request->get("access_token");
$token_status = Array();
$id_status = Array();
if (!empty($id_token)) {
// Check that the ID Token is valid.
try {
// Client library can verify the ID token.
$jwt = $client->verifyIdToken($id_token, CLIENT_ID)->getAttributes();
$gplus_id = $jwt["payload"]["sub"];
$id_status["valid"] = true;
$id_status["gplus_id"] = $gplus_id;
$id_status["message"] = "ID Token is valid.";
} catch (Google_AuthException $e) {
$id_status["valid"] = false;
$id_status["gplus_id"] = NULL;
$id_status["message"] = "Invalid ID Token.";
}
$token_status["id_token_status"] = $id_status;
}
$access_status = Array();
if (!empty($access_token)) {
$access_status["valid"] = false;
$access_status["gplus_id"] = NULL;
// Check that the Access Token is valid.
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' .
$access_token;
$req = new Google_HttpRequest($reqUrl);
$tokenInfo = json_decode(
$client::getIo()->authenticatedRequest($req)
->getResponseBody());
if ($tokenInfo->error) {
// This is not a valid token.
$access_status["message"] = "Invalid Access Token.";
} else if ($tokenInfo->audience != CLIENT_ID) {
// This is not meant for this app. It is VERY important to check
// the client ID in order to prevent man-in-the-middle attacks.
$access_status["message"] = "Access Token not meant for this app.";
} else {
$access_status["valid"] = true;
$access_status["gplus_id"] = $tokenInfo->user_id;
$access_status["message"] = "Access Token is valid.";
}
$token_status["access_token_status"] = $access_status;
}
return $app->json($token_status, 200);
});
$app->run();
I did like this
composer install (after that run this command)
composer dump-autoload
it is working for me
If your Framework don't work after migration.
or see Class 'Silex\Application' not found.
Delete "vendor" folder after composer install.
Working for me

Laravel Token Signature could not be verified

I'm using Laravel/Lumen as an API for the backend of a webapp and run into a hiccup.
In an example I have a route that does not need the user to be authenticated. But I do want to check in the routes controller if the user visiting has a valid token.
So I wrote the following:
if ($tokenFetch = JWTAuth::parseToken()->authenticate()) {
$token = str_replace("Bearer ", "", $request->header('Authorization'));
} else {
$token = '';
}
I believe the above will check the Bearer token is valid else it will return a blank variable.
The following is my entire Controller.
public function show($url, Request $request)
{
if ($tokenFetch = JWTAuth::parseToken()->authenticate()) {
$token = str_replace("Bearer ", "", $request->header('Authorization'));
} else {
$token = 'book';
}
return response()->json(['token' => $token]);
}
The Problem
If I a pass in a valid Token Bearer, it returns the token but if I pass in an invalid one I get the following error:
TokenInvalidException in NamshiAdapter.php line 62:
Token Signature could not be verified.
If I don't pass a token at all:
JWTException in JWTAuth.php line 195:
The token could not be parsed from the request
Is there a way to check if a token is passed and if it has then check if its valid, but also if one has not been passed then return a blank return?
You can wrap it inside try/catch block
public function show($url, Request $request)
{
try {
$tokenFetch = JWTAuth::parseToken()->authenticate())
$token = str_replace("Bearer ", "", $request->header('Authorization'));
}catch(\Tymon\JWTAuth\Exceptions\JWTException $e){//general JWT exception
$token = 'book';
}
return response()->json(['token' => $token]);
}
There are few exceptions that you might want to handle separately (jwt-auth/Exceptions)
Also as you're using laravel 5 you can global handling for JWT exceptions ,not recommended in this case but you should know of this option and choose yourself. app/Exceptions/Handler.php and inside render method add [at the top]
if ($e instanceof \Tymon\JWTAuth\Exceptions\JWTException) {
//what happen when JWT exception occurs
}
Yes it's possible to achieve what you want.
Check if a token is passed:
If you check in the documentation of parseToken you'll see that the algorithm to check if we pass a token is:
if (! $token = $this->parseAuthHeader($header, $method)) {
if (! $token = $this->request->query($query, false)) {
}
}
// which it will be in your case:
$hasToken = true;
$header = $request->headers->get('authorization');
if (! starts_with(strtolower('authorization'), 'bearer')) {
if (! $request->query('token', false)) {
$hasToken = false;
}
}
Check if a token is valid:
Please note that the NamshiAdapter use the Namshi\JOSE package so read the documentation here.
In NamshiAdapter.php as you can see the line who rise your error are:
if (! $jws->verify($this->secret, $this->algo)) {
throw new TokenInvalidException('Token Signature could not be verified.');
}
// in your case:
// + try to var_dump $this->secret, $this->algo
// + use Namshi\JOSE\JWS
// if you var_dump
$jsw = new JWS(['typ' => 'JWT', 'alg' => $algo]);
$jws = $this->jws->load($token, false);
// if you want to follow the documentation of Namshi\JOSE
$jws = JWS::load($tokenString, false, $encoder, 'SecLib');
// again var_dump for $this->secret, $this->algo
$isValidToken = ($jws->verify($this->secret, $this->algo));

Laravel JWT tokens are Invalid after refresh them in a authentication JWT approach

EDIT:
Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83
MY ORIGINAL QUESTION:
I'm implement with jwt-auth my protected resources that require an authenticated user with bellow code:
Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function() {
// Protected routes
});
When user 'sign in' on API an Authorization token is created, and sent on response Authorization header to client application that call the resource. So, client applications when intercept a Authorization token on header of any response, set a variable/session/whatever with this token value, to send again to API on next request.
The first request for a protected resource after 'login' works fine, but the next client application request to API with a refreshed token, gives the following error (API mount all responses in json format):
{
"error": "token_invalid"
}
What can be happen with refreshed tokens? My refresh token implementation (set as a after middleware) is wrong? Or isn't necessary to manually refresh all Authorization token that come with client apps requests?
UPDATE:
I update the jwt-auth RefreshToken middleware as propose here, but the token_invalid persist.
BUG:
I guess that I found what happens. Note that in the refresh method, old token is added to blacklist cache case enabled:
// Tymon\JWTAuth\JWTManager
public function refresh(Token $token)
{
$payload = $this->decode($token);
if ($this->blacklistEnabled) {
// invalidate old token
$this->blacklist->add($payload);
}
// return the new token
return $this->encode(
$this->payloadFactory->setRefreshFlow()->make([
'sub' => $payload['sub'],
'iat' => $payload['iat']
])
);
}
And note that in add to blacklist method the key is the jti param from old token payload:
// Tymon\JWTAuth\Blacklist
public function add(Payload $payload)
{
$exp = Utils::timestamp($payload['exp']);
// there is no need to add the token to the blacklist
// if the token has already expired
if ($exp->isPast()) {
return false;
}
// add a minute to abate potential overlap
$minutes = $exp->diffInMinutes(Utils::now()->subMinute());
$this->storage->add($payload['jti'], [], $minutes);
return true;
}
Thus, when has on blacklist method is called, the old token jti param is the same that the new, so the new token is in blacklist:
// Tymon\JWTAuth\Blacklist
public function has(Payload $payload)
{
return $this->storage->has($payload['jti']);
}
If you don't need the blacklist functionality just set to false on jwt.php configuration file. But I can't say if it expose to some security vulnerability.
Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83
When I get this issue, the solution that I found to get my project working was to generate a new token with data from older token on each new request.
My solution, that works for me, is bad, ugly, and can generate more issues if you have many async requests and your API(or business core) server is slow.
For now is working, but I will investigate more this issue, cause after 0.5.3 version the issue continues.
E.g:
Request 1 (GET /login):
Some guest data on token
Request 2 (POST /login response):
User data merged with guest data on old token generating a new token
Procedural code example(you can do better =) ), you can run this on routes.php out of routes, I say that is ugly haha:
// ----------------------------------------------------------------
// AUTH TOKEN WORK
// ----------------------------------------------------------------
$authToken = null;
$getAuthToken = function() use ($authToken, $Response) {
if($authToken === null) {
$authToken = JWTAuth::parseToken();
}
return $authToken;
};
$getLoggedUser = function() use ($getAuthToken) {
return $getAuthToken()->authenticate();
};
$getAuthPayload = function() use ($getAuthToken) {
try {
return $getAuthToken()->getPayload();
} catch (Exception $e) {
return [];
}
};
$mountAuthPayload = function($customPayload) use ($getLoggedUser, $getAuthPayload) {
$currentPayload = [];
try {
$currentAuthPayload = $getAuthPayload();
if(count($currentAuthPayload)) {
$currentPayload = $currentAuthPayload->toArray();
}
try {
if($user = $getLoggedUser()) {
$currentPayload['user'] = $user;
}
$currentPayload['isGuest'] = false;
} catch (Exception $e) {
// is guest
}
} catch(Exception $e) {
// Impossible to parse token
}
foreach ($customPayload as $key => $value) {
$currentPayload[$key] = $value;
}
return $currentPayload;
};
// ----------------------------------------------------------------
// AUTH TOKEN PAYLOAD
// ----------------------------------------------------------------
try {
$getLoggedUser();
$payload = ['isGuest' => false];
} catch (Exception $e) {
$payload = ['isGuest' => true];
}
try {
$payload = $mountAuthPayload($payload);
} catch (Exception $e) {
// Make nothing cause token is invalid, expired, etc., or not exists.
// Like a guest session. Create a token without user data.
}
Some route(simple example to save user mobile device):
Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function () use ($getLoggedUser, $mountAuthPayload) {
Route::post('/session/device', function () use ($Response, $getLoggedUser, $mountAuthPayload) {
$Response = new \Illuminate\Http\Response();
$user = $getLoggedUser();
// code to save on database the user device from current "session"...
$payload = app('tymon.jwt.payload.factory')->make($mountAuthPayload(['device' => $user->device->last()->toArray()]));
$token = JWTAuth::encode($payload);
$Response->header('Authorization', 'Bearer ' . $token);
$responseContent = ['setted' => 'true'];
$Response->setContent($responseContent);
return $Response;
});
});

Categories