How to enable offline access to customer data in google adwords api php - google-api

Google Adwords API (PHP Client)
I am trying to get a user to authorize once on my website to be able to get his data for analytical purposes. But I can not figure out a way to do it with the documentation it is quite complex.
Do I need to add them to my mcc to be able to do this or is there another way using something like https://developers.google.com/identity/protocols/OAuth2WebServer

You will have to connect the user via oauth token with offline access activated, so that the request returns a refresh token, that you can use to programmatically access the connection, even if the user is not logged into you application:
$client->setApprovalPrompt('force');
$client->setAccessType('offline');
As explained here https://developers.google.com/adwords/api/docs/guides/authentication you can use the oauth playground from groogle to test the api and see what you need.
Additionally here is an example for the client connection method (partially Laravel specific code, but should be good enough to explain the procedure):
function googleIntegrationClient($refresh=false)
{
$client = new \Google_Client();
$client->setClientId(env('GOOGLE_OAUTH_CLIENT_ID'));
$client->setClientSecret(env('GOOGLE_OAUTH_CLIENT_SECRET'));
$client->setRedirectUri(env('GOOGLE_OAUTH_REDIRECT_URI'));
$client->setApplicationName('App Google Integration');
$client->addScope("profile");
$client->addScope("email");
$client->addScope("https://www.googleapis.com/auth/adwords");
//make sure there is a refresh token in the request for offline access.
$client->setApprovalPrompt('force');
$client->setAccessType('offline');
if($refresh)
{
//get currently logged in user
$user = \Auth::user();
//set token from user data
$client->setAccessToken($user->settings["integration"]["google"]['token_data']);
//check if token is valid
if($client->isAccessTokenExpired())
{
//as token is invalid set refresh token
$token_data = json_decode($user->settings["integration"]["google"]['token_data']);
$client->refreshToken($token_data->refresh_token);
//save new token data
$modify_settings = $user->settings;
$modify_settings["integration"]["google"]["token_data"] = $client->getAccessToken();
$user->settings = $modify_settings;
$user->save();
}
}
return $client;
}
You can use this method then in your oauth connecting routine:
//route for redirecting to google oauth
public function redirectToProvider()
{
$user = \Auth::User();
$client = googleIntegrationClient();
$auth_url = $client->createAuthUrl();
return \Redirect::to(filter_var($auth_url, FILTER_SANITIZE_URL));
}
//callback route to handle the provided data from google oauth
public function handleProviderCallback(Request $request)
{
$user = \Auth::User();
$client = googleIntegrationClient();
$data = $request->all();
if (isset($data['code']))
{
try {
$client->authenticate($data['code']);
$token = $client->getAccessToken();
} catch (Exception $e) {
$user->settings = array(
"integration" => array(
"google" => array(
'active' => false,
)));
$user->save();
}
if($token)
{
$google_oauth = new \Google_Service_Oauth2($client);
$user->settings = array(
"integration" => array(
"google" => array(
'active' => true,
'token_data' => $token,
'id' => $google_oauth->userinfo->get()->id,
'avatar' => $google_oauth->userinfo->get()->picture,
'email' => $google_oauth->userinfo->get()->email,
'name' => $google_oauth->userinfo->get()->name,
)));
$user->save();
}
}
}
Good luck!

Related

Authorization code grant with Socialite setup

I'm trying to implement an Authorization code grant with Socialite. I'm able successfully to receive users in the callback, but struggling to set up the other part of auth. I've created the personal passport client and received the client ID & Secret in oauth_clients. Whenever I use the createToken() I am able to see oauth_access_tokens these tokens with the name of PKCE(which contains the same name as in oauth_client). The $code is taken from the query in the callback, however, the $response in the very end is null, any idea why?
public function callbackGoogle()
{
// Receive the user credentials from social
$googleUser = Socialite::driver('google')->stateless()->user();
// Get the user by email
$user = User::where('email', $googleUser->email)->first();
// Create token
$user->createToken('PKCE')->accessToken;
// Get query parameter code from callback
$code = request()->query()['code'];
// Get client data
$client = DB::table('oauth_clients')->where('name', 'PKCE')->first();
$response = Http::asForm()->post('http://localhost:5000/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => $client->id,
'client_secret' => $client->secret,
'code' => urldecode($code),
'redirect_uri' => $client->redirect,
]);
// // Receiving null
dd($response->json());
}
You are trying to exchange an access token from google with your own application.
This does not make sense.
With this line:
$googleUser = Socialite::driver('google')->stateless()->user();
You have already exchanged the code from the URL with a token from google.
If you want to get an API token for your API, you already did this with
$user->createToken('PKCE')->accessToken;
You could use
$token = $user->createToken('PKCE')->accessToken;

Laravel 8 SSO implementation

I am tring to implement a SSO structure:
main application with all user to manage the login (sso-app)
multiple application that will authenticate to sso-app (app1, app2, ...)
I managed to make the base login with sso-app api using Laravel Passport package.
Here the app1 controller for the authorization process:
class SSOController extends Controller
{
public function getLogin(Request $request){
$request->session()->put("state", $state = Str::random(40));
$query = http_build_query([
'client_id' => env('SSO_CLIENT_ID'),
'redirect_uri' => env('APP_URL') . '/auth/callback',
'response_type' => 'code',
'scope' => '',
'state' => $state
]);
return redirect(env('SSO_HOST') . '/oauth/authorize?' . $query);
}
public function getCallback(Request $request){
$state = $request->session()->pull('state');
throw_unless(strlen($state) > 0 && $state == $request->state,
InvalidArgumentException::class
);
$response = Http::asForm()->post(
env('SSO_HOST') . '/oauth/token',
[
'grant_type' => 'authorization_code',
'client_id' => env('SSO_CLIENT_ID'),
'client_secret' => env('SSO_SECRET'),
'redirect_uri' => env('APP_URL') . '/auth/callback',
'code' => $request->code
]
);
$request->session()->put($response->json());
$token = $response->json()['access_token'];
$jwtHeader = null;
$jwtPayload = null;
$parsed_token = parse_jwt($token);
try{
$email = $parsed_token->payload->user->email;
}
catch(\Throwable $e){
return redirect('login')->withError("Failed to get login information! Try again.");
}
$user = User::firstOrCreate(['email' => $email], array_merge((array)$parsed_token->payload->user, ['name' => ($parsed_token->payload->user->first_name." ".$parsed_token->payload->user->last_name)]));
Auth::login($user);
return redirect(route('home'));
}
}
The app1 will redirect to sso-app login form than when user successfull login he will redirect back to app1.
Everything work as aspected, but how can I use this approach to authorize the api route?
This work only for the "web" guard because I had create a local user table for every app and made the login based on session as you can see on the end of SSOController.
But how can I use the token returned from sso-app to authenticate local app1, app2, ... api?
Should I have to create a middleware that call sso-app every time I call app1 api to check if the token is valid or there is a better approach to save time and increase speed?
Thanks.

Satellizer Facebook Login 'Wrong Number of Segments error' LARAVEL

I have been playing with JWT Tokens lately, using Firebase\JWT and Satelizer.
I have been trying to integrate with laravel-angular project I have been working on.
Here is the sample code from Satelizers.io for facebook login: (https://github.com/sahat/satellizer/blob/master/examples/server/php/app/Http/Controllers/AuthController.php)
/**
* Login with Facebook.
*/
public function facebook(Request $request)
{
$client = new GuzzleHttp\Client();
$params = [
'code' => $request->input('code'),
'client_id' => $request->input('clientId'),
'redirect_uri' => $request->input('redirectUri'),
'client_secret' => Config::get('app.facebook_secret')
];
// Step 1. Exchange authorization code for access token.
$accessTokenResponse = $client->request('GET', 'https://graph.facebook.com/v2.5/oauth/access_token', [
'query' => $params
]);
$accessToken = json_decode($accessTokenResponse->getBody(), true);
// Step 2. Retrieve profile information about the current user.
$fields = 'id,email,first_name,last_name,link,name';
$profileResponse = $client->request('GET', 'https://graph.facebook.com/v2.5/me', [
'query' => [
'access_token' => $accessToken['access_token'],
'fields' => $fields
]
]);
$profile = json_decode($profileResponse->getBody(), true);
// Step 3a. If user is already signed in then link accounts.
if ($request->header('Authorization'))
{
$user = User::where('facebook', '=', $profile['id']);
if ($user->first())
{
return response()->json(['message' => 'There is already a Facebook account that belongs to you'], 409);
}
$token = explode(' ', $request->header('Authorization'))[1];
$payload = (array) JWT::decode($token, Config::get('app.token_secret'), array('HS256')); // STUCK HERE
$user = User::find($payload['sub']);
$user->facebook = $profile['id'];
$user->email = $user->email ?: $profile['email'];
$user->displayName = $user->displayName ?: $profile['name'];
$user->save();
return response()->json(['token' => $this->createToken($user)]);
}
// Step 3b. Create a new user account or return an existing one.
else
{
$user = User::where('facebook', '=', $profile['id']);
if ($user->first())
{
return response()->json(['token' => $this->createToken($user->first())]);
}
$user = new User;
$user->facebook = $profile['id'];
$user->email = $profile['email'];
$user->displayName = $profile['name'];
$user->save();
return response()->json(['token' => $this->createToken($user)]);
}
}
I am able to get the Facebook profile inforamtion, but I keep getting stuck on this line:
$payload = (array) JWT::decode($token, Config::get('app.token_secret'), array('HS256')); // STUCK HERE
It is throwing a 'Wrong number of segments' error when trying to read the taken set by satelizer.
I have inspected the token and it does not follow the typical JWT format. (there are no 'dots' in the token)
Has anyone expereinced this before? How do I go about solving this issue?
Thanks,
Matt

Laravel Socialite - Avatar is to slow

I am using the Laravel 5.0 with the Socialite Library. Everything works fine except i am a little disappointment with the size of the avatar.
Is it possible to get a bigger avatar?
Looking at the source code of Socialite https://github.com/laravel/socialite/blob/2.0/src/Two/FacebookProvider.php
You can see at line 91 that the url for the avatar appends a static ?type=normal at the end. The Facebook graph API documentation says that you can request an avatar size using an ENUM or custom width/height so you can modify line 91 and append an ENUM or custom width/height like ?type=large
More details can be found in the Facebook graph API documentation.
However, this is only for the Facebook driver so you will need to dig in a similar fashion for other providers. If their APIs do not allow such freedom as in the case of Facebook then you will need to do with the avatar being returned.
Updated March 12, 2015
Do not customize the original Socialite package, fork the repository and then make the change. You can then plug your forked repository into your project and also send a pull request to the original author in case he thinks its worth having the functionality you have implemented. Also, your forked repository will not be updated/maintained as is the case with the original package. In case you choose to update to the new package, your changes will be lost.
Fix for Facebook / Google / Twitter Small Avatar Photo
I have created this helper method on my AuthController
public function getBigAvatar($user, $provider)
{
return ($provider == "google") ? $user->getAvatar()."0" : $user->avatar_original;
}
AND THIS IS HOW I CALL IT:
$user = Socialite::driver( $provider )->user();
$userPhoto = $this->getBigAvatar($user, $provider);
So Simple in case its Google, Well just append 0 to the end of the url and well get a 500px avatar. And for twitter and Facebook, The Providers already offers an avatar_original attribute as seen in
FacebookProvider.php
protected function mapUserToObject(array $user)
{
$avatarUrl = $this->graphUrl.'/'.$this->version.'/'.$user['id'].'/picture';
return (new User)->setRaw($user)->map([
'id' => $user['id'], 'nickname' => null, 'name' => isset($user['name']) ? $user['name'] : null,
'email' => isset($user['email']) ? $user['email'] : null, 'avatar' => $avatarUrl.'?type=normal',
'avatar_original' => $avatarUrl.'?width=1920',
]);
}
TwitterProvider.php
return $instance->map([
'id' => $user->uid, 'nickname' => $user->nickname,
'name' => $user->name, 'email' => $user->email, 'avatar' => $user->imageUrl,
'avatar_original' => str_replace('_normal', '', $user->imageUrl),
]);
Since Google does not map this, And gets a default 50px image, we simple change it to 500px with is great for avatar.
WORKED PERFECT FOR ME, LET ME KNOW IF IT HELPS YOU!
Just append your required size after the ?type=normal like this:
graph.facebook.com/v2.2/{user}/picture?type=normal &width=76&height=76
this will override the type=normal
For use original avatars size from Facebook and Google:
public function handleProviderCallback($provider){
$userData = Socialite::driver($provider)->user();
$user = new User;
...
Google:
$user->avatar = preg_replace('/\?sz=[\d]*$/', '', $userData->avatar);
Facebook:
$user->avatar = $userData->avatar_original;
here is another simpler way for the avatar
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
public function handleProviderCallback($provider)
{
$user = Socialite::driver($provider)->user();
// dd($user);
if ($user) {
$authUser = $this->findOrCreateUser($user, $provider);
Auth::login($authUser, true);
return redirect()->route('home');
}
return 'something went wrong';
}
private function findOrCreateUser($user, $provider)
{
$found = User::where('provider_id', $user->id)->first();
if ($found) {
return $found;
}
// so the default is G+, change according to your needs
$avatar = str_replace('?sz=50', '', $user->avatar);
if ($provider == 'facebook' || $provider == 'twitter') {
$avatar = $user->avatar_original;
}
return User::create([
'username' => $user->name,
'email' => $user->email,
'avatar' => $avatar,
'provider' => $provider,
'provider_id' => $user->id,
]);
}
this way u dont change ur code much and easier for maintenance ,
however for some reason the facebook avatar doesnt show up in my app , plz if anyone can help i would deeply appreciate it.
here is my the current fb avatar link i get
https://graph.facebook.com/v2.6/xxxxxxxxxxx/picture?width=1920
and the view
<img class="user-avatar" src="{{ $user->avatar }}" alt="user avatar">
Here is code I have came up with
if($file = $user->getAvatar()) {
if ($provider == 'google') {
$file = str_replace('?sz=50', '', $file);
} elseif ($provider == 'twitter') {
$file = str_replace('_normal', '', $file);
} elseif ($provider == 'facebook') {
$file = str_replace('type=normal', 'type=large', $file);
}
}
Happy coding everyone :)
This is basically just replacement or a part of avatar image URL so it would return the bigger one.
getAvatar() function return the image url, so I stored it in variable $file
And depending on the provider url structure is different, so for each provider you need to change image URL string accordingly
Google
remove '?sz=50' from image url string
Twitter
remove '_normal' from image url string
Facebook
replace 'type=normal' with 'type=large' in image url string

How-to Form confirmation response

I am a Laravel newbie. I have gone through tutorials successfully...this is my first production app for a client, using Laravel
I have a form and am able to get the submitted data into a database. The user is given a generic error though. After successful submission, I cannot redirect the user to a confirmation page. Any help is appreciated.
Here is the code from my controller (the store function):
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$rules = array(
'lastname' => 'required',
'email' => 'required|email',
'phone' => 'required',
'date' => 'date|date_format:"m/d/Y"'
);
$validator = Validator::make(Input::all(), $rules);
// process the login
if ($validator->fails()) {
return Redirect::to('nerds/create')
->withErrors($validator)
->withInput(Input::except('password'));
}
else
{
// store
$registration = new Registration;
$registration->firstname = Input::get('firstname');
$registration->lastname = Input::get('lastname');
$registration->email = Input::get('email');
$registration->date = date("Y-m-d", strtotime(Input::get('date')));
$registration->phone = Input::get('phone');
$registration->venue = Input::get('venue');
$registration->venueCity = Input::get('venueCity');
$registration->save();
// redirect
Session::flash('message', 'Successfully submitted, someone will contact you soon!');
return Redirect::to('thankyou');
}
}
Please make sure the app/storage folder has the full permissions. Apache should be able to write to that folder.
There must be a route to handle the 'Redirect::to'
The tutorial I followed failed to mention this fact.
Along with the code from my controller's store() function, the following route is necessary.
Route::get('/registration/thankyou', function()
{
return View::make('thankyou');
});

Resources