I've been developing a feature for an extension in TYPO3 to post activities to a Google Plus domain profile.
I use the following code to instantiate the Google Client
$googleClient = new Google_Client();
$googleClient->setApplicationName("NAME");
$googleClient->setClientId("123456789");
$googleClient->setClientSecret("qwertyuiop");
$googleClient->setRedirectUri("CALLBACK_URL");
$googleClient->setScopes(array(
'https://www.googleapis.com/auth/plus.me',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.stream.write',
));
$googleClient->setRequestVisibleActions('http://schema.org/AddAction');
$googleClient->setApprovalPrompt('force');
$googleClient->setAccessType('offline');
All values are like token are verified.
Then store the profiles obtaining the refreshToken to make the POST many times. Now the code of POST
$googleClient->refreshToken($this->refreshToken);
$googleClient->verifyIdToken();
$plusdomains = new Google_Service_PlusDomains($googleClient);
$post = new Google_Service_PlusDomains_Activity();
$post['object']['originalContent'] = 'HELLO WORLD';
try {
$result = $plusdomains->activities->insert('me', $post);}
catch (\Exception $e){
var_dump($e);
}
The line that is inside the try generates an error that is:
Access to the Google+ Domains API is not allowed as the user has
consented to incompatible scopes. See:
https://developers.google.com/+/domains/authentication/
I have searched for additional information about the error, which indicates that are the permissions or scopes, even in other questions of this same forum a few years ago. However I checked on https://developers.google.com/+/domains/authentication/scopes and the ones I'm using are there. I would appreciate you guiding me in solving this problem.
Related
I'm trying the following thing for quite a while now and am heavily struggling...
On a website, I first want to authenticate a user with his Google Account using OAuth. Therefore, I'm using this library. In order to get it working, I used $f3->set('AUTOLOAD','vendor/ikkez/f3-opauth/lib/opauth/'); to load the PHP files and then used the following code to create the routes and make the authentication possible:
$f3 = \Base::instance();
// load opauth config (allow token resolve)
$f3->config('vendor/ikkez/f3-opauth/lib/opauth/opauth.ini', TRUE);
// init with config
$opauth = OpauthBridge::instance($f3->opauth);
// define login handler
$opauth->onSuccess(function($data){
header('Content-Type: text');
//$data['credentials']['token'];
});
// define error handler
$opauth->onAbort(function($data){
header('Content-Type: text');
echo 'Auth request was canceled.'."\n";
print_r($data);
});
So far so good, thats all working fine, once permission is granted from Google I get the correct callback, also including the login token.
Now the next step is, that after user gave permission for that (by authenticating), I want to check, if the user subscribed to a specific channel on Youtube (and afterwards saving that information to my DB, printing it at the first step would be enough though).
Now I did my homework for multiple hours in trying to figuring out how it works...
What I (in general found) is that the following curl request should give me the desired result:
curl \
'https://youtube.googleapis.com/youtube/v3/subscriptions?part=snippet%2CcontentDetails&forChannelId=UC_x5XG1OV2P6uZZ5FSM9Ttw&mine=true&key=[YOUR_API_KEY]' \
--header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
--header 'Accept: application/json' \
--compressed
I then tried to sent this curl request with PHP, substituting the API KEY with my Google API Key and "YOUR_ACCESS_TOKEN" with the token I got from OAUTH.... However, it's throwing an error, saying "request had insufficient authentication scopes"... That seems to be because when checking the PHP example from Google, I have to provide the Scopes I'm using - in my case https://www.googleapis.com/auth/youtube.readonly.
The PHP code provided by Google is the following:
<?php
/**
* Sample PHP code for youtube.subscriptions.list
* See instructions for running these code samples locally:
* https://developers.google.com/explorer-help/guides/code_samples#php
*/
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
throw new Exception(sprintf('Please run "composer require google/apiclient:~2.0" in "%s"', __DIR__));
}
require_once __DIR__ . '/vendor/autoload.php';
$client = new Google_Client();
$client->setApplicationName('API code samples');
$client->setScopes([
'https://www.googleapis.com/auth/youtube.readonly',
]);
// TODO: For this request to work, you must replace
// "YOUR_CLIENT_SECRET_FILE.json" with a pointer to your
// client_secret.json file. For more information, see
// https://cloud.google.com/iam/docs/creating-managing-service-account-keys
$client->setAuthConfig('YOUR_CLIENT_SECRET_FILE.json');
$client->setAccessType('offline');
// Request authorization from the user.
$authUrl = $client->createAuthUrl();
printf("Open this link in your browser:\n%s\n", $authUrl);
print('Enter verification code: ');
$authCode = trim(fgets(STDIN));
// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);
// Define service object for making API requests.
$service = new Google_Service_YouTube($client);
$queryParams = [
'forChannelId' => 'UC_x5XG1OV2P6uZZ5FSM9Ttw',
'mine' => true
];
$response = $service->subscriptions->listSubscriptions('snippet,contentDetails', $queryParams);
print_r($response);
This let's me run into a new issue... Trying to use this code, I'm getting the error, that Google_Client is not known as class... I then went ahead and installed Google Client with Composer and tried to use vendor/autoload.php in order to use the class.... However, when including the autoload.php, I get the error Fatal error: Cannot declare class Prefab, because the name is already in use... This seems to be the case, because the f3-opauth declares this Prefab class already and then the google apiclient tries to declare it again... However, I didn't manage to to include google apiclient without the autoload...
You see, I really tried a lot and I've been working on this for about 5-6 hours today, only getting that one API request to work and I don't know what else to try...
Any hint on how to get it working would be appreciated - if there's any hint on doing it a completely other way, I'd be willing to change it as well, as the project itself just started.
Summarizing, what I'm trying to do is the following:
-> User can log in on Website with his Youtube/Google Account
-> When authenticating, its checked, if the User is a Subscriber of a specific channel. Next step would be to also check, if he is a channel member of this speicific channel. Both information would need to be saved to database
-> after that, user can always log in into his account with Google again and in the database, you can find the information if the user is subscriber and/or channel member of this channel..
Thanks in advance!
I'm not sure if this will help with your exact use case, but I've worked with Google APIs in the past with Fat-Free. I couldn't get it to work right off the bat, so I installed and got it working with the Google Client/API/SDK. Once I got that working, then I worked backwards to see if I could make it work with Fat-Free. One of the things that I noticed I was running into was missing fields in the Oauth request. access_type was one that got me as well as approval_prompt. I know that you said you've gotten your access token thus far, so it may not apply, but it could for future requests. Here's some example code I've got working to generate an oauth URL for Google Sign in, and then to process the request and make the call to the userinfo portion.
<?php
class App_Auth {
public static function generateOauthUrl() {
$fw = Base::instance();
$Oauth = new \Web\OAuth2();
$Oauth->set('client_id', $fw->get('google.client_id'));
$Oauth->set('scope', 'profile email');
$Oauth->set('response_type', 'code');
$Oauth->set('access_type', 'online');
$Oauth->set('approval_prompt', 'auto');
$Oauth->set('redirect_uri', $fw->SCHEME.'://' . $_SERVER['HTTP_HOST'] . $fw->BASE.'/oauthRedirect');
return $Oauth->uri('https://accounts.google.com/o/oauth2/auth', true);
}
public static function processAuthCodeAndGetToken($auth_code) {
$fw = Base::instance();
$Oauth = new \Web\OAuth2();
$Oauth->set('client_id', $fw->get('google.client_id'));
$Oauth->set('client_secret', $fw->get('google.client_secret'));
$Oauth->set('scope', 'profile email');
$Oauth->set('access_type', 'online');
$Oauth->set('grant_type', 'authorization_code');
$Oauth->set('code', $auth_code);
$Oauth->set('approval_prompt', 'auto');
$Oauth->set('redirect_uri', $fw->SCHEME.'://' . $_SERVER['HTTP_HOST'] . $fw->BASE.'/oauthRedirect');
return $Oauth->request('https://oauth2.googleapis.com/token', 'POST');
}
public static function getOauthUserInfo($access_token) {
$Oauth_User_Info = new \Web\OAuth2();
return $Oauth_User_Info->request('https://www.googleapis.com/oauth2/v2/userinfo', 'GET', $access_token);
}
One other error that has bitten me in the backside was we would get our access token from Google and then store it in the database for subsequent requests. We would get that scopes error you mentioned request had insufficient authentication scopes. We eventually figured out that the access_token was longer than our database field (VARCHAR(32) if I remember right) so we needed to make our database field longer so it would store the whole thing.
Hopefully one of those triggers something for you to figure out your issue.
THE SITUATION:
I am testing the Gmail API for my app.
I have tested some requests and they are working fine.
For example get messages, get user history, get draft list etc..
Basically all the read only requests are working fine.
I have instead some issues related with permission with other requests, for example when i have to write or delete a draft.
This is the error i get:
(403) Insufficient Permission
THE CODE:
This is the function to initialize the app:
public function gmail_init_service()
{
$client = new Google_Client();
$client->setApplicationName("Gmail API test");
$client->setDeveloperKey("MY_KEY");
$client->setClientSecret('MY_CLIENT_SECRET');
$client->SetClientId('MY_CLIENT_ID');
$client->setScopes(array('https://mail.google.com/'));
$client->setAccessToken('{"access_token":"MY_ACCESS_TOKEN","token_type":"Bearer","expires_in":3600,"refresh_token":"MY_REFRESH_TOKEN","created":1433502343}');
$service = new Google_Service_Gmail($client);
return $service;
}
This is the request to delete one draft:
public function gmail_delete_draft()
{
$service = $this->gmail_init_service();
// --------------- Get draft list --------------
$list = $service->users_drafts->listUsersDrafts('me');
$draftList = $list->getDrafts();
// --------------- Get draft IDs ---------------
$inbox_draft = [];
foreach($draftList as $mlist)
{
$draftId = $mlist->id;
$optParamsGet2['format'] = 'full';
$single_message = $service->users_drafts->get('me', $draftId , $optParamsGet2);
$inbox_draft[]['draftId'] = $draftId;
$inbox_draft[]['draft'] = $single_message;
}
// --------------- Delete draft ---------------
$draft_delete = $service->users_drafts->delete('me', 'DRAFT_ID' );
}
EDIT:
I have tried to revoke the permission and setup new credentials.
The scope declared when initializing the service is:
https://mail.google.com/
that as stated in the documentation grant full access to the account.
But i am still getting the same error.
The same exact error for the following requests:
Delete draft - Create draft - Create label - Delete message
THE QUESTION:
Why am i getting that error?
It has to do with same values store in a cache?
Or is related with permission of the API?
You need 'https://www.googleapis.com/auth/gmail.compose' to create a draft. So what happens if you
$client->setScopes(array(
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.compose'
));
or if you want to get more formal
define('SCOPES', implode(' ', array(
Google_Service_Gmail::MAIL_GOOGLE_COM,
Google_Service_Gmail::GMAIL_COMPOSE)
));
$client->setScopes(SCOPES)
or whatever the valid php might be (I haven't done php for a while).
Note that if you have a token already you might have to do some calisthenics to revoke it so you can reissue with the added permissions. That might mean deleting a file, perhaps named gmail.storage or, if you have access to the account login and make your way to https://security.google.com/settings/security/permissions the access permissions can be manually revoked.
This link might be relevant:
https://developers.google.com/gmail/api/auth/scopes
And a meander through the source code might be enlightening.
You might be able to glean some insight from my battle with this same sort of thing under Python
You need https://mail.google.com/ as your scope to be able to delete mail.
I have an android client, that initiate log-in to facebook, receives access token and other details about the profile.
The android client passes the access_token and details to the Server (PHP).
Both have facebook sdk installed.
When I initiate a FacebookRequest from the PHP, for example '/me/' It's working.
BUT when I initiate a (friends who have installed the APP) FacebookRequest from the PHP
'/me/friends'. I get "null".
When I use the graph explorer provided by facebook the result is :
{
"data": [
]
}
Additional information for the helpers:
The app contains only two users at the moment, which are friends on facebook.
Those users are both administrators of the app.
Currently the app is not live\published.
We haven't "Start a Submission" in Stats and Reviews as suggested somewhere.
We asked for the permission 'user_friends'.
Since everything in stack overflow requires reputation,
This is how the permissions look like:
http://i.stack.imgur.com/kURwB.png
Thanks
EDIT:
OK I added two test users, Made them friends of each other, and /me/friends through graph explorer is working for them.
BUT why doesn't it work for non-test-users?
Okay,
So apparently Facebook had a problem providing new Access Tokens.
Don't know why, but it provided the same access token, for new Log-Ins.
Now that they fixed that here is a Handy function that I wrote in PHP that will let users
Parse Facebook responses, this might come in hand since there is NOTHING explained over the Docs, Google, StackOverFlow.
use Facebook\FacebookSession;
use Facebook\FacebookRequest;
function find_my_facebook_friends($access_token){
require_once 'facebook-php-sdk-v4-4.0-dev/autoload.php';
FacebookSession::setDefaultApplication('app_key','secret_key');
$session = new FacebookSession($access_token);
$request = new FacebookRequest(
$session,
'GET',
'/me/friends'
);
$response = $request->execute();
$graphObject = $response->getGraphObject()->asArray();
return $graphObject;
}
Usage:
include_once 'path/to/function.php';
//You wanna use it, just so the PHP file contains your app_key, secret_key Wont be exposed.
$access_token = isset($_POST['access_token']) ? $_POST['access_token'] : NULL;
$facebook_object = find_my_facebook_friends($access_token);
for($i = 0 ; $i < count($facebook_object['data']) ; $i++){
$id = get_object_vars($facebook_object['data'][$i]);
echo $id['id'];
echo $id['name'];
//You don't have to echo, you could handle those fields as you wanted.
}
This function refers to a Mobile Client passing his Log In User's access token to the server.
I hope this will save you lots of time!
I have a browser-based app (single page, AngularJS) and am using hello to use third party signin such as Google, FB, Soundcloud, etc.
My app uses a PHP API server.
What's a good way to have the user able to login using Google, but also verify the user on the server side?
I was considering:
The browser app performs an implicit grant with google/fb/etc
I then transfer the access_token from the client to the server, then use, for example, a google-api-php-client with my app id, secret and the user access_token? Using their API such as /me? (which grant type would this be?)
Retrieve some key from the third-party (facebook_id, email, etc), match it against a user in my database, and then consider the user authenticated?
Also, should I perform this on each API request? Or should I just stash the access_token for a bit and assume that the user is still valid until the key expires?
One issue is that not all of those providers support the implicit flow. But assuming they do, the access_token you get for each will be proof that the user authenticated with that system, not necessarily that they have access to call your API. You still need something that asserts that "someone#gmail.com can 'read' resource X in your system"
You probably need something that translates whatever you get from Google, Soundcloud, etc. into a token your app understands. A simple(r) format is to use JWT. (Json Web Tokens).
App -> Intermmediary -> Soundcloud/Google
<-JWT--+ <---whavetever-+
and then:
App - (JWT) -> API
JWT are easy to manipulate, validate and verify. See jwt.io
You might want to look at this blog post also for some additional information (specifically on AngularJS front-ends)
The blog post #eugenio-pace mentioned was really helpful for setting up the client side.
For the server side though, the access_token should be validated.
The SDK's are (in composer) (code below):
Facebook: "facebook/php-sdk-v4" : "4.0.*"
Google: cURL request (didn't care for "google/apiclient")
SoundCloud: "ise/php-soundcloud": "3.*"
(There are others of course, just these three were the ones I chose, and seem decent.)
Last time I did something like this I made the mistake of validating the access_token on every request, which had a huge (obviously negative) impact on performance. Now I just validate it on login and use it to retrieve the user's ID from that service. So, the browser sends me access_token A and says it's from Facebook, I use the sdk above the the access_token with Facebook, and I get back their ID so I know they are who they say they are.
I'd suggest storing the access_token on the server with the expires_in.
(I haven't dealt with refresh token's yet)
Code to validate tokens using the above libraries:
function validateTokenFacebook($token, $id=null) {
// Performed above
// FacebookSession::setDefaultApplication($config->fb->app_id, $config->fb->secret);
$session = new FacebookSession($token);
// Fetch user info
$request = new FacebookRequest($session, 'GET', '/me');
try {
$response = $request->execute();
} catch (\Facebook\FacebookServerException $e) {
$this->mlog->err($e . "\n" . $e->getTraceAsString());
throw new AuthTokenInvalidException();
}
$graphObject = $response->getGraphObject();
$user_id = $graphObject->getProperty('id');
return array(access_token, $user_id);
}
function validateTokenGoogle($token, $id=null) {
$resp=array();
// This key isn't included in the token from hello.js, but
// google needs it
if (!array_key_exists('created', $token)) $token['created'] = $token['expires'] - $token['expires_in'];
$client = new \Google_Client();
$client->setClientId($this->systemConfig->google->app_id);
$client->setClientSecret($this->systemConfig->google->secret);
$client->setRedirectUri($this->systemConfig->google->redirectUri);
$client->setScopes('email');
$client->setAccessToken(json_encode($token));
try {
// Send Client Request
$objOAuthService = new \Google_Service_Oauth2($client);
$userData = $objOAuthService->userinfo->get();
return array($token['access_token'], $userData['id']);
} catch (\Google_Auth_Exception $e) {
throw new AuthException('Google returned ' . get_class($e));
}
}
function validateTokenSoundcloud($token, $id=null) {
$soundcloud = new \Soundcloud\Service(
$this->systemConfig->soundcloud->app_id,
$this->systemConfig->soundcloud->secret,
$this->systemConfig->soundcloud->redirect);
$soundcloud->setAccessToken($access_token);
try {
$response = json_decode($soundcloud->get('me'), true);
if (array_key_exists('id', $response))
return array($access_token, $response['id']);
} catch (Soundcloud\Exception\InvalidHttpResponseCodeException $e) {
$this->mlog->err($e->getMessage());
}
throw new AuthTokenInvalidException();
}
I have a few custom classes above, such as the Exceptions and the systemConfig, but I think it's verbose enough to communicate what they do.
When I try to update the profile description of a soundcloud account via their php sdk, I get a 403 error every time. The app is authenticated and I am able to do things like place comments, but I'm not able to update anything on the profile (particularly the description field).
I'm using the standard code, found in their official documentation:
<?php
require_once 'Services/Soundcloud.php';
// create a client object with access token
$client = new Services_Soundcloud('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET');
$client->setAccessToken('YOUR_ACCESS_TOKEN');
// get the current user
$user = json_decode($client->get('me'));
// update the user's profile description
$user = json_decode($client->post('me', array(
'description' => 'I am using the SoundCloud API!'
)));
print $user->description;
Please help me find out where the error comes from, because I'm all out of ideas.
Our bad, the user documentation that you point to there had two problems:
Updates to the user resource should use the PUT method, not POST.
Arguments need to be namespaced properly.
I've modified the documentation to fix these two problems. New code sample:
<?php
require_once 'Services/Soundcloud.php';
// create a client object with access token
$client = new Services_Soundcloud('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET');
$client->setAccessToken('ACCESS_TOKEN');
// get the current user
$user = json_decode($client->get('me'));
// update the user's profile description
$user = json_decode($client->put('me', array(
'user[description]' => 'I am using the SoundCloud API!'
)));
print $user->description;
Hope that helps and sorry again for the confusion. Let me know if you run into any more problems.
To update user related information you need to login to Sound Cloud as user and then authenticate your application to use your personal data, else you will get 403 for all user related information while updating / delete. For posting any comments you don't need this authentication
http://developers.soundcloud.com/docs#authentication
Refer
Getting Information about the Authenticated User
Once the user has signed into SoundCloud and approved your app's authorization request, you will be able to access their profile and act on their behalf. We have provided a convenient endpoint for accessing information about the authenticated user.