How to use Oauth2 Provider instead of Oauth1 in Laravel Socialite? - php

I'm currently developing social listening API with twitter, the flow is logged in user (using basic API Token to send request to the API) are redirected to twitter app and log in to twitter account through redirect link provided by Laravel Socialite. After successfully login the account info will be saved in MySQL along with user who registered the account.
I'm using Laravel 7 with Socialite v5.4.0
i've beend trying to provide query parameters in the callback url using :
Socialite::driver('twitter')
->redirectUrl('https://some-site-url.com/twitter/login/callback?api_token={sample-token}')
->redirect()->getTargetUrl());
but Socialite return error Call to undefined method Laravel\\Socialite\\One\\TwitterProvider::redirectUrl()",
thats when i realized that the currently used twitter provider is using Oauth1 Laravel\Socialite\One\TwitterProvider. when i look at the vendor in Laravel\Socialite\\SocialiteManager.php its creating instance of this :
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\One\AbstractProvider
*/
protected function createTwitterDriver()
{
$config = $this->config->get('services.twitter');
return new TwitterProvider(
$this->container->make('request'), new TwitterServer($this->formatConfig($config))
);
}
but in the same Laravel\Socialite\\SocialiteManager.php its also have method to create Oauth2 instance like below :
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createTwitterOAuth2Driver()
{
$config = $this->config->get('services.twitter');
return $this->buildProvider(
TwitterOAuth2Provider::class, $config
);
}
Questions
now my question is how to force the Socialite::driver('twitter') method to use Oauth2 instead of Oauth1 which basically is available in the Socialite itself ? i have tried to override the method but found no link to which instance is calling SocialiteManager.php, so currently i have only tried to modify the vendor function to return Oauth2 AbstractProvider (which i know its really ugly approach but i feel really curious), its like this :
// /**
// * Create an instance of the specified driver.
// *
// * #return \Laravel\Socialite\One\AbstractProvider
// */
// protected function createTwitterDriver()
// {
// $config = $this->config->get('services.twitter');
// return new TwitterProvider(
// $this->container->make('request'), new TwitterServer($this->formatConfig($config))
// );
// }
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createTwitterDriver()
{
$config = $this->config->get('services.twitter');
return $this->buildProvider(
TwitterOAuth2Provider::class, $config
);
}
.the method works and its return the redirect url successfully but failed to logged in to twitter page for unknown reason, which make me think is there a way to cleanly switch Socialite provider version between Oauth1 and Oauth2. Or is there any alternative to provide a callback with user identifier instead ?
the url return after i ditch the method in vendor SocialiteManager.php
But failed to log in to twitter app for unknown reason
thanks in advance, it's my first question and i've been looking for the answer since yesterday but found no specific way to switch Socialite provider between version 1 and version 2

Laravel\Socialite\SocialiteManager.php looks for an oauth key in your twitter credentials to determine whether to use OAuth1 or Oauth2
/**
* Create an instance of the specified driver.
*
* #return \Laravel\Socialite\One\AbstractProvider
*/
protected function createTwitterDriver()
{
$config = $this->config->get('services.twitter');
if (($config['oauth'] ?? null) === 2) {
return $this->createTwitterOAuth2Driver();
}
return new TwitterProvider(
$this->container->make('request'), new TwitterServer($this->formatConfig($config))
);
}
Simply add an oauth key with a value of 2 to your Twitter credentials in the config/services.php file.
'twitter' => [
'client_id' => env('TWITTER_CLIENT_ID'),
'client_secret' => env('TWITTER_CLIENT_SECRET'),
'redirect' => env('TWITTER_REDIRECT_URL'),
'oauth' => 2
],
If you are switching from oauth1 do not forget to update your credentials to use OAuth 2.0 Client ID and Client Secret which you can get from your Twitter developer account.

Related

Add/Embed Instagram Posts To Laravel 7

What I want: I am creating a blog using Laravel 7 for my client. He wants to add a section containing a link to his Instagram posts by something similar to a carousel, so that, when he posts a new Instagram post the website will be automatically updated.
What I did:
I went to developers.facebook.com, created a new app and setup Instagram Basic Display where I got Instagram App Id, Secret and Name.
Added Client OAuth Settings to be for localhost, it forced me to add ssl (https) I know this won't work, I will change it to the actual domain later, and I hope for a way to test that on localhost too. .
I added the Instagram tester and authorized it and generated an access token.
I installed composer require dymantic/laravel-instagram-feed from github-repo
I ran the command php artisan vendor:publish which showed me these options
Which provider or tag's files would you like to publish?:
[0 ] Publish files from all providers and tags listed below
[1 ] Provider: Dymantic\InstagramFeed\InstagramFeedServiceProvider
[2 ] Provider: Facade\Ignition\IgnitionServiceProvider
So I entered 1 the I ran php artisan migrate; which created the following file I updated the client id and secret to the data shown in the first photo I linked:
// config/instagram-feed.php
<?php
return [
/*
* The client_id from registering your app on Instagram
*/
'client_id' => 'YOUR INSTAGRAM CLIENT ID',
/*
* The client secret from registering your app on Instagram,
* This is not the same as an access token.
*/
'client_secret' => 'YOUR INSTAGRAM CLIENT SECRET',
/*
* The route that will respond to the Instagram callback during the OAuth process.
* Only enter the path without the leading slash. You need to ensure that you have registered
* a redirect_uri for your instagram app that is equal to combining the
* app url (from config) and this route
*/
'auth_callback_route' => 'instagram/auth/callback',
/*
* On success of the OAuth process you will be redirected to this route.
* You may use query strings to carry messages
*/
'success_redirect_to' => 'instagram-auth-success',
/*
* If the OAuth process fails for some reason you will be redirected to this route.
* You may use query strings to carry messages
*/
'failure_redirect_to' => 'instagram-auth-failure'
/*
* You may filter out video media types by setting this to true. Carousel media
* will become the first image in the carousel, and if there are no images, then
* the entire carousel will be ignored.
*/
'ignore_video' => false,
/*
* You may set an email address below if you wish to be notified of errors when
* attempting to refresh the Instagram feed.
*/
'notify_on_error' => null,
];
I added the following trait after running the command php artisan instagram-feed:profile my-insta-username:
<?php
namespace App\Traits;
use \Dymantic\InstagramFeed\Profile;
trait Instagram
{
public static function feed()
{
$profile = Profile::where('username', 'my-insta-username')->first();
$feed = $profile->feed();
dd($profile);
}
}
Result and Conclusion: Obviously this didn't work, function $profile->feed(); is unknown since the profile would be only a collection we don't have access to the Instagram API yet.
Question: Any idea to get the needed data how can I benefit from what I already did, even if I can get the same result using a curl request it would be fine??
THANK YOU :)
If what you want to achieve is to embed your Ig feed images into a carousel I'd recommend you to use postaddic/instragram package (https://github.com/postaddictme/instagram-php-scraper).
With this package you can easily fetch the URL of the image like so:
$instagram = Instagram::withCredentials('login', 'password', new Psr16Adapter('Files'));
$instagram->login();
$instagram->saveSession();
$posts = $instagram->getFeed();
foreach ($posts as $post){
echo $post->getImageHighResolutionUrl()."\n";
}
If you want to test, I found this was the simplest method for fetching public accounts:
$instagram = new \InstagramScraper\Instagram();
// For getting information about account you don't need to auth:
$account = $instagram->getAccount('neymarjr');
If the profile is public even better since you can use methods that dont require your credentials for authentication.
The hassle from embbeding photos directly from Ig API is not required.

yii2: problems using google/apiclient for oAuth and token authentication

My project is built on Yii2 and uses google/apiclient for the purposes of login to the web interface. There's also an android app which connects to the API and uses bearer authentication against Google tokens (which I believe pulled in firebase/jwt). This has worked fine since early 2018 until the week commencing 10th September 2018. No code was changed in my system.
Since then, attempting to login to the web interface (oAuth) gives
yii\authclient\InvalidResponseException: Request failed with code: 400, message: {
"error" : "redirect_uri_mismatch",
"error_description" : "Bad Request"
The site is correctly listed in the Google developer console (where I've also not changed anything) so the redirect_uri_mismatch is not expected.
Upgrading google/apiclient allows me to login to the web interface but breaks the app's token auth, giving:
Your request was made with invalid credentials
I can provide the full stack trace if required, however, I'm hoping someone else has encountered the same and can point me in the right direction. Using firebase/jwt v4 in the live system allows token auth to function but using v4 in test with the upgraded google/apiclient fails the auth with the same credentials error as above.
Can anyone provide any guidance please?
To get refresh token on first login you need to set "?access_type=offline" in "authUrl". And then save it somewhere, in database, files etc.
If you have no database or if you ok with prompting access rights every time on login in you can add "&approval_prompt=force" to same URL. Thats will enforce Google send you refresh token every time you are logging in, but also enforces showing access rights prompt every time.
class MyGoogleClient extends yii\authclient\clients\Google
{
/**
* Set to true if you want to get refresh token always on login
* #var type
*/
public $enforceRefreshToken = false;
/**
* {#inheritdoc}
*/
public function init()
{
parent::init();
if (is_array($this->scope)) {
$this->scope = implode(' ', $this->scope);
}
$additionalParams = [];
if ($this->autoRefreshAccessToken) {
$additionalParams['access_type'] = 'offline';
}
if ($this->enforceRefreshToken) {
$additionalParams['approval_prompt'] = 'force';
}
if (!empty($additionalParams)) {
$this->authUrl = $this->composeUrl($this->authUrl, $additionalParams);
}
}
/**
* {#inheritdoc}
* #return string return URL.
*/
protected function defaultReturnUrl()
{
return Yii::$app->getUrlManager()->createAbsoluteUrl([Yii::$app->controller->getRoute(), 'authclient' => 'google']);
}
}

Laravel 5.3 with Passport: Get Current User with Personal Access Token

I've installed Passport with Laravel 5.3 and configured to use Personal Access Token. I am sending username and password to `/oauth/token' route and get a token. Everything is fine until here.
But if I want to get current logged in user with $user = Auth::user(); as we used to get it before, I get null value as Laravel don't keep any session for API Token. In this situation, how can I get the current user? Can I modify any file to get the current user along with token?
Thanks In Advance Arif
First of all, I'm using Passport with password_clients in an OAuth2 flow. My Lumen/Laravel version in composer is 5.4.*
I'm using the following statement in my Lumen application (note I'm not using facades):
$user = app('auth')->guard()->user()
This is probably equivalent to the facade call:
$user = Auth::guard()->user()
Hope this helps..
Kind regards,
PS. To figure what class is returned when not using facades, I find myself doing eg. a echo get_class(app('auth')) to get the class and tell my IDE what kind of variable $auth is..
Snippet from one of my Lumen controllers
$auth = app('auth');
// echo get_class($auth); to get class
// -> \Illuminate\Auth\AuthManager in this case
/* #var $auth \Illuminate\Auth\AuthManager */
$guard = $auth->guard();
// idem as above
/* #var $guard \Illuminate\Auth\RequestGuard */
$user = $guard->user();
/* #var $auth \App\Auth\User */
// this tells Webstorm/Netbeans/.. to consider $auth an instance of AuthManager and enables autocompletion of class methods

laravel - Google Analytics API Authentication

I am trying to create a web-app with a simple dashboard with Analytics data for the accounts who logged in with Google. I am using Laravel with Socialite package, and I can log the users in with Google currently. I have my developer client-key and client-secret. I set scopes for Analytics read-only, and offline access, and I store customer name, email, Google ID, access token and refresh token in my database. I can log the user in without any problem.
What I want to do is for now, just access the profiles an Analytics account currently has. I followed Analytics API documentation examples but could not get it working. Since I am storing an access token and a refresh token, I think I should be able to authenticate the current user and get their Analytics data, but I could not find any simple methods from the Client and Analytics libraries. I will need to access their Analytics data offline, and this is why I think I should be able to authorize my requests with access token and refresh token, but I do not get any Analytics specific data from user login process. I am completely lost now, how do I authorize my requests to Anayltics API? I have been using AdWords API for more than 8 months, and everything is crystal clear in AdWords API documentation, but I could not get anything working with Analytics API.
These are my user login methods:
public function redirectToProvider()
{
$parameters = ['access_type' => 'offline'];
return Socialite::driver('google')
->scopes(['https://www.googleapis.com/auth/analytics.readonly'])
->with($parameters)
->redirect();
}
/**
* Obtain the user information from Google.
*
* #return Response
*/
public function handleProviderCallback()
{
$outsiderLogin = Socialite::driver('google')->stateless()->user();
$user = User::where('googleID', $outsiderLogin->id)->first();
// Register the user if there is no user with that id.
if (!$user) {
$user = new User;
$user->name = $outsiderLogin->name;
$user->googleID = $outsiderLogin->id;
$user->email = $outsiderLogin->email;
$user->token = $outsiderLogin->token;
$user->refreshToken = $outsiderLogin->refreshToken;
$user->save();
}
// Log the user in.
Auth::login($user);
return redirect('/home');
}
Thank you very much.
I have found the solution for now. At first, I figured that I needed the code that returns with authentication URL from Google, and when I inspect the Socialite package, I have found a protected method getCode() in \vendor\laravel\socialite\src\Two\AbstractProvider.php, which returns the code from the URL. I edited the source file of the package and changed the method type from protected to public, and that made it possible to use that method outside of the class, which allowed me to access the code from the URL, then store it in DB for further authentication requirements. But there were issues with this setup, first of all, I should find a way to keep that package without any update, since any update will rollback the changes I made to the source file. The second problem I faced was the way I store tokens. By default, Google Client API returns an array which contains the fields access_token, refresh_token, expires_in, id and created, and with these fields, it authenticates the requests to Analytics server. In my scenario, there were no standard array returning from the basic Socialite login process. There were access_token, refresh_token and expires variables and I stored them all in my database as well. This caused an issue with Google library, it asked for a structured array and I did not even have the variables expires_in and created, this is why I setup a fake array which tells Google to refresh token with every request, and this was not a good practice either.
At the end, I could not understand how to use any package online and I wrote my own simple authentication, and I do not know if it has any vulnerabilities, but it works for me, it may also work for those who needs it.
Here are my routes lines:
Route::get('auth/google', [
'as' => 'googleLogin',
'uses' => 'Auth\AuthController#redirectToProvider'
]);
Route::get('auth/google/callback', [
'as' => 'googleLoginCallback',
'uses' => 'Auth\AuthController#handleProviderCallback'
]);
And these are the AuthController methods:
/**
* Redirect the user to the Google authentication
*/
public function redirectToProvider()
{
// Create the client object and set the authorization configuration from JSON file.
$client = new Google_Client();
$client->setAuthConfig('/home/vagrant/Analytics/client_secret.json');
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/auth/google/callback');
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->addScope("email");
$client->addScope("profile");
$client->setAccessType("offline");
$auth_url = $client->createAuthUrl();
return redirect($auth_url);
}
/**
* Obtain the user information from Google.
*
* #return redirect to the app.
*/
public function handleProviderCallback()
{
// Handle authorization flow from the server.
if (! isset($_GET['code'])) {
return redirect('auth/google');
} else {
// Authenticate the client, and get required informations.
$client = new Google_Client();
$client->setAuthConfig('/home/vagrant/Analytics/client_secret.json');
$client->authenticate($_GET['code']);
// Store the tokens in the session.
Session::put('token', $client->getAccessToken());
$service = new Google_Service_Oauth2($client);
$userInfo = $service->userinfo->get();
$user = User::where('googleID', $userInfo->id)->first();
// If no match, register the user.
if(!$user) {
$user = new User;
$user->name = $userInfo->name;
$user->googleID = $userInfo->id;
$user->email = $userInfo->email;
$user->refreshToken = $client->getRefreshToken();
$user->code = $_GET['code'];
$user->save();
}
Auth::login($user);
return redirect('/home');
}
}
I have placed the client_secret.json file I have downloaded from Google API Console into the specified folder, this may be different for you. I have also modified the migration file in order to match the required segemnts. After these steps, I am able treat that user as it is a simple user that registered with the basic Laravel auth.
Now I can query, say, the accounts in the user's Google Analytics account like this:
/**
* #var $client to be authorized by Google.
*/
private $client;
/**
* #var $analytics Analytics object to be used.
*/
private $analytics;
public function __construct()
{
$this->client = $this->AuthenticateCurrentClient();
$this->analytics = new Google_Service_Analytics($this->client);
}
private function AuthenticateCurrentClient(){
$user = Auth::user();
$token = Session::get('token');
// Authenticate the client.
$client = new Google_Client();
$client->setAccessToken($token);
$client->authenticate($user->code);
return $client;
}
public function GetAccounts(){
try {
$accountsObject = $this->analytics->management_accounts->listManagementAccounts();
$accounts = $accountsObject->getItems();
return $accounts;
} catch (apiServiceException $e) {
print 'There was an Analytics API service error '
. $e->getCode() . ':' . $e->getMessage();
} catch (apiException $e) {
print 'There was a general API error '
. $e->getCode() . ':' . $e->getMessage();
}
}
There were thousands of times Stack Overflow has helped me, I hope this helps someone to get things working.
You're not really going to find what you're looking for with the Socialite package shipped with Laravel (which is more used for logins and that's about it).
You can however find many Google Analytic packages (along with many other Laravel bundles) here which should help you make API calls:
http://packalyst.com/s/google%20analytics
More specifically, this package: https://github.com/spatie/laravel-analytics
That, or run your own Guzzle and cURL scripts. I use Guzzle when I need something quick without building a full blown API.
However, there's an interesting post here about using Socialite to access GA data. But you're quite limited. If you're creating user driven dashboards, I'd opt for a separate package.
https://laracasts.com/discuss/channels/tips/how-i-made-google-analytics-work-with-socialite
I'am also trying to do the same thing. By far, I've user authentication at place with oAuth 2.0 and Socialite package. I need the list of sites to be fetched from GA. And am totally stuck there. It would really be great if you can guide me as on how should I move on further..

Luracast Restler Multiple Authentication Classes Not Allowing Access

I have two authentication classes defined.
API Keys (APIKeyAuth)
OAUTH2 (OAUTH2Server)
In my index.php I have the following defined
$r = new Restler();
$r->addAuthenticationClass('APIKeyAuth');
$r->addAuthenticationClass('OAUTH2Server');
I then protect one of the rest methods for APIKeyAuth
/**
* #access protected
* #class APIKeyAuth{#requires apikey}
*/
public function .......etc
If I debug it , it goes through the first step and $authObj (see code below from restler.php) will
be APIKeyAuth. It checks __isAllowed and returns true ... which is good.
It then however goes through OAUTH2Server (which in my opinion it shouldn't as the rest method has
been decorated to use APIKeyAuth.
So it goes through and __isAllowed in OAUTH2Server is false so then the user will get a Unauthorzied response.
foreach ($this->authClasses as $authClass) {
$authObj = Scope::get($authClass);
if (!method_exists($authObj,
Defaults::$authenticationMethod)
) {
throw new RestException (
500, 'Authentication Class ' .
'should implement iAuthenticate');
} elseif (
!$authObj->{Defaults::$authenticationMethod}()
) {
throw new RestException(401);
}
}
Do I need to alter the OAUTH2 Server to check if its using an API Key and add logic ? (seems wrong approach).
Restler upto RC5 handles authentication classes serially, meaning that all the authentication classes must return true to go through the protected api call
Since RC6 this has changed to parallel, meaning that any one of the authentication class can allow access to the protected api

Categories