Laravel 5.1 - Facebook Authentication through Socialite - facebook

I am trying to implement Facebook Login to my project however I receive an error:
ClientException in Middleware.php line 69:
Client error: 400
in /Applications/MAMP/htdocs/laratest/vendor/guzzlehttp/guzzle/src/Middleware.php line 69
at Middleware::GuzzleHttp{closure}(object(Response)) in Promise.php line 199
at Promise::callHandler('1', object(Response), array(object(Promise), object(Closure), null)) in Promise.php line 152
at Promise::GuzzleHttp\Promise{closure}() in TaskQueue.php line 60
at TaskQueue->run() in CurlMultiHandler.php line 96
Steps I've gone through:
composer require laravel/socialite & composer update.
In my config>services.app,
'facebook' => [
'client_id' => env('FB_CLIENT_ID'),
'client_secret' => env('FB_SECRET_ID'),
'redirect' => 'http://localhost.com:8888',
],
Added
Laravel\Socialite\SocialiteServiceProvider::class, & 'Socialite' => Laravel\Socialite\Facades\Socialite::class, in config>app.php
Set up the routes successfully.
Route::get('auth/facebook', 'Auth\AuthController#redirectToProvider');
Route::get('auth/facebook', 'Auth\AuthController#handleProviderCallback');
Setup the link successfully in my blade file. Login with Facebook
In my Controller>AuthController.php, I added:
use Laravel\Socialite\Facades\Socialite;
** Beside everything that AuthController has, inside the AuthController class, I added:**
public function redirectToProvider()
{
return Socialite::driver('facebook')
->scopes(['scope1', 'scope2'])->redirect();
}
/**
* Obtain the user information from Facebook.
*
* #return Response
*/
public function handleProviderCallback()
{
$user = Socialite::driver('facebook')->user();
// $user->token;
}
Also users table:
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username');
$table->string('name');
$table->string('email')->unique();
$table->string('password', 60);
$table->string('avatar');
$table->string('provider');
$table->string('provider_id');
$table->rememberToken();
$table->timestamps();
});
}
Edit:
When I comment out $user = Socialite::driver('facebook')->user(); part, I get redirected to localhost.com/auth/facebook
Edit 2:
My .env file:
'facebook' => [
FB_CLIENT_ID => '###',
FB_SECRET_ID => 'this-is-secret!',
],

I think your two routes should not have the same URL, because Laravel has no way to know which to use.
Facebook returns a ?code parameter, so make use of it! If ?code is included, then handle the login logic from Socialite, if not, redirect to Facebook.
You have two unknown scopes in your code, scope1 and scope2, those are unknown to Facebook and will return an error when trying to access them. Use real scopes like public_profile or email and you should be set. (scopes are the permissions you are requesting)

Make sure Laravel is calling properly your config entries with
php artisan config:clear
And also check your .env file for FB_CLIENT_ID and FB_SECRET_ID entries. Probably client's failing because it's trying to connect to facebook without client and secret, falling into an exception.

I've had the same problem(ClientException in Middleware.php line 69: Client error: 400).
I just switch off "Require App Secret " in the facebook's advanced settings and this solved the problem. Maybe this information will be helpful.

I had a similar problem with Facebook APi - it gave Response code 400
and Ive just tried:
composer require laravel/socialite & composer update
and it updated dependencis:
Updating dependencies (including require-dev)
- Removing laravel/socialite (v2.0.11)
- Installing laravel/socialite (v2.0.14)
Im Using Laravel 5.1:
composer.json:
"laravel/framework": "5.1.*",
"laravel/socialite": "^2.0"
And it seems to work fine now
Hope this helps someone

Related

How to Disallow User Access to CRUD Backend

I've got Backpack for Laravel installed and have been using it for some time as an admin back end on a project. I'm also using the spatie/permission module (might come with Backpack, can't remember) to create users for the front end.
Currently, all users are able to access both front end and back end regardless of the group they belong to. I'd like to change that so that only members in an "admin" group are able to access the back end. I've followed the instructions here for separating front end and back end sessions but that's not really what I want as all users are still able to access both sites of the project.
I'm guessing I need to add a guard to the CRUD routes but I'm finding it to be much harder than it should be. Any pointers on how to do this would be greatly appreciated. TIA.
You can create a new middleware and use it in your routes group for admin routes.
To create a new middleware use the php artisan command like so: (you can name the new middleware what ever you want:
php artisan make:middleware RequireAdminRole
Now, inside your new middleware, on the handle function you can have something like this that returns a 403 Forbidden error message:
public function handle($request, Closure $next)
{
$user = auth()->user();
if (!$user) return $next($request);
if (!$user->hasRole('Admin'))
{
// if your sessions are decoupled from the frontend
// you can even logout the user like so:
// auth()->logout();
abort(403, 'Access denied');
}
return $next($request);
}
Here we are using the hasRole method, but there are more that you can use. See the spatie/laravel-permissions documentation for more info.
Now, let's assign this middleware a 'name' so we can use it in our route groups for the admin. Inside the App\Kernel.php file, in the end, inside the $routeMiddleware array add it and give it a new, for example:
'isadmin' => \App\Http\Middleware\RequireAdminRole::class,
And finally, you can add this middleware to your admin routes group (which should be in custom.php file if you're using latest backpack 3.4 version) :
Route::group([
'prefix' => 'admin',
'middleware' => ['web', 'isadmin', config('backpack.base.middleware_key', 'admin')],
'namespace' => 'App\Http\Controllers\Admin',
], function () {
// your routes are here
});
Now all your requests to the admin routes should be protected by the user role check.
Please let us know how it went for you, and if you encountered any issues.
Best regards,
~Cristian

Laravel 5.4 mongodb passport access token Unauthenticated

I've built an example Laravel project using mongodb (Jenssegers Mongodb) and I use passport in Laravel 5.4 follow by this document.
Everything work fine until I took an access token to Postman for test, now take a look at my api.php route
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
In postman, I setup two headers that are Accept: application/json and Authorization: Bearer $TOKEN and I'm very sure that my access token is not the copy missing fault but still getting error.
{
"error": "Unauthenticated."
}
Things I've tried
I have overwrite default id field in model User.php
use Authenticatable, Authorizable, CanResetPassword, Notifiable, HasApiTokens;
protected $collection = 'users';
protected $fillable = ['username', 'email', 'password', 'name'];
protected $primaryKey = '_id';
I also modify the token expiration time in AuthserviceProvider.php like so
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addYears(20));//You can also use addDays(10)
Passport::refreshTokensExpireIn(Carbon::now()->addYears(20));//You can also use addDays(10)
Passport::pruneRevokedTokens(); //basic garbage collector
Passport::tokensCan([
'conference' => 'Access your conference information'
]);
}
And some other ways but still not work.
UPDATE for debug infomation
When I add try catch in to public/index.php, an error apperead
League\OAuth2\Server\Exception\OAuthServerException: The resource owner or authorization server denied the request. in /data/www/public_html/xxxx/vendor/league/oauth2-server/src/Exception/OAuthServerException.php:165
Stack trace:
#0 /data/www/public_html/xxxx/vendor/league/oauth2-server/src/AuthorizationValidators/BearerTokenValidator.php(66): League\OAuth2\Server\Exception\OAuthServerException::accessDenied('Access token ha...')
#1 /data/www/public_html/xxxx/vendor/league/oauth2-server/src/ResourceServer.php(82): League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator->validateAuthorization(Object(Zend\Diactoros\ServerRequest))
......
When I check file vendor\league\oauth2-server\src\AuthorizationValidators\BearerTokenValidator.php at line 66. Seems my access token has been revoked, but in my database revoked column still false, and this access token is brand new, I just created in few minutes ago.
if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) {
throw OAuthServerException::accessDenied('Access token has been revoked');
}
Any idea?
After few hours of digging deep in to vendor\league\oauth2-server\src\AuthorizationValidators\BearerTokenValidator.php I was found a solution.
The problem is in file vendor\laravel\passport\src\TokenRepository.php at function public function find($id) line 27
public function find($id)
{
return Token::find($id);
}
Because I use Mobodb and this function is use use Jenssegers\Mongodb\Eloquent\Model and it search for _id instead of id. So, just need to make change find function like this.
public function find($id)
{
return Token::where('id', $id)->first();
}
Although modify vendor file is not recommend, but in this case I have to do that, hopefully in next version, author of Laravel passport will release a passport support mongoDB.
Hope this help someone.

Laravel Socialite: InvalidStateException in AbstractProvider.php line 199:

For some reason I get this exception when I try to log in with Laravel Socialite with either Facebook or Google:
InvalidStateException in AbstractProvider.php line 199:
The exception are thrown from my SocialiteController, when it tries to get the user from the facebook driver.
public function callback(SocialAccountService $service, $provider)
{
try {
var_dump(Socialite::driver('facebook')->user());
Here are the part of AbstractProvider.php that seem to throw the actual exception:
public function user()
{
if ($this->hasInvalidState()) {
throw new InvalidStateException;
}
I have been following this tutorial https://blog.damirmiladinov.com/laravel/laravel-5.2-socialite-twitter-login.html#.WFK0BfnhCUk. The login have worked fine until last week, for some reason.
I read other articles saying I should change config/session.php so domain is not null but my current domain (in my case localhost:8000 since I run local with XAMPP), and refresh Laravel cache etc. But it did not work.
I have discovered that my Laravel application cookies where missing, which caused the InvalidStateException exception.
I also noticed that the cookies were not recreated after each HTTP request. When I changed the domain value in Config/Session.php from my current one ("localhost") to null (the default value), then the cookies were recreated again.
Try
session()->put('state', $request->input('state'));
$user = Socialite::driver('facebook')->user();

Looking for a start-to-finish how-to on Laravel 5.2 OAuth2 implementation

Quick background: I'm fairly experienced with PHP, but needed to build my first RESTful API. I figured I'd try Laravel (5.2) and am starting to feel pretty comfortable with it.
I started adding auth to my project over the weekend and I am really struggling to get it working. I got the basic Laravel Auth middleware working quickly, but I think I need to be using OAuth2 for production (I will be building a mobile app that will connect up to this server). I'm using the Luca Degasperi OAuth2 package, which seems to be pretty popular.
I reviewed the actual documentation: https://github.com/lucadegasperi/oauth2-server-laravel/tree/master/docs#readme)
I also went through this tutorial: https://medium.com/#mshanak/laravel-5-token-based-authentication-ae258c12cfea#.5lszb67xb
And, most recently, I found this thread about the need to seed the OAuth tables before anything will work: https://github.com/lucadegasperi/oauth2-server-laravel/issues/56
That's all great, but there are some minor differences in the most recent distribution of Laravel. For example, /app/Http/Kernel.php is slightly different from what's shown in some of the examples I found because it now uses middleware groups. I thought I handled those differences correctly (I added the OAuthExceptionHandlerMiddleware class to the 'web' section of $middlewareGroups instead of $middleware). I got my seeder working (the current oauth_scopes table only allows you to supply a description, so I had to slim down what was provided in the third link above).
If I put a test route in my 'web' group in routes.php, I would have thought this would require OAuth because I added OAuth to the 'web' middleware group in Kernel.php. That's not the case. My route works with no authentication if I do that.
I then explicitly added the OAuth middleware to my test route as follows:
Route::get('tests/events', ['middleware' => 'oauth', function() {
$events = App\Event::get();
return response()->json($events);
}]);
That causes a 500 error ("ErrorException in OAuth2ServerServiceProvider.php line 126: explode() expects parameter 2 to be string, object given").
I'm to feel pretty lost. Each of these packages seems to be shifting so quickly that there's no complete documentation on how to get this up and running.
What else do I need to do to get this functioning?
The following link is what finally got me un-stuck:
https://github.com/lucadegasperi/oauth2-server-laravel/blob/master/docs/authorization-server/password.md
Now that I have it working, I'll try and make this a complete how-to FOR PASSWORD GRANT TYPES ONLY. I didn't play with other grant types. So this assumes you're building something like a RESTful API where users will connect to it with a client app that you're going to build. So users will create a user account in your system and then when they send a REST request, the OAuth2 package will authenticate them and send them a token to stay logged in.
I'm using Laravel 5.2 and already had the basic Auth package up and running. Be advised that a lot of these steps seem to change even with incremental releases of Laravel or the OAuth2 package.
The first part of getting this working is fairly well documented already (https://github.com/lucadegasperi/oauth2-server-laravel/tree/master/docs#readme), but here's a summary just in case...
Edit the require section of your composer.json file to look something like this:
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.2.*",
"lucadegasperi/oauth2-server-laravel": "5.1.*"
},
Run composer update to download the package.
Open your config/app.php file and add the following two lines to the end of the providers section:
LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider::class,
LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider::class,
Also in config/app.php, add this line to the aliases array:
'Authorizer' => LucaDegasperi\OAuth2Server\Facades\Authorizer::class,
Now we start to do things a little differently from the documentation to accommodate the current version of Laravel...
Open app/Http/Kernel.php. Laravel now uses groups and it didn't used to. Update your $middlewareGroups to look like this:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
//Added for OAuth2 Server
\LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware::class,
//Commented out for OAuth2 Server
//\App\Http\Middleware\VerifyCsrfToken::class,
],
'api' => [
'throttle:60,1',
],
];
Also in app/Http/kernel.php, update $routeMiddleware to look like this:
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
//Added for OAuth2 Server
'oauth' => \LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware::class,
'oauth-user' => \LucaDegasperi\OAuth2Server\Middleware\OAuthUserOwnerMiddleware::class,
'oauth-client' => \LucaDegasperi\OAuth2Server\Middleware\OAuthClientOwnerMiddleware::class,
'check-authorization-params' => \LucaDegasperi\OAuth2Server\Middleware\CheckAuthCodeRequestMiddleware::class,
'csrf' => App\Http\Middleware\VerifyCsrfToken::class,
];
You now have to set up your grant types. You used to do this all in one place in config\oauth2.php using an array with a closure for callback. With the most recent version of the OAuth2 server package, you can't use a closure for callback anymore. It has to be a string. So your grant_types should look something like this:
'grant_types' => [
'password' => [
'class' => '\League\OAuth2\Server\Grant\PasswordGrant',
'callback' => '\App\PasswordGrantVerifier#verify',
'access_token_ttl' => 3600
]
]
access_token_ttl is the duration that an auth token will be good for (in seconds). The main package documentation uses 3600 (1 hour) by default. You might want to try 604800 (1 week) instead -- at least during testing.
You now need to create the PasswordGrantVerifier class and verify method that you just called in the code section above. So you create a file App/PasswordGrantVerifier.php and use the following code (which is basically what used to go in the closure for callback).
<?php
namespace App;
use Illuminate\Support\Facades\Auth;
class PasswordGrantVerifier
{
public function verify($username, $password)
{
$credentials = [
'email' => $username,
'password' => $password,
];
if (Auth::once($credentials)) {
return Auth::user()->id;
}
return false;
}
}
You will need at least one row in the oauth_clients table before OAuth2 will work. You can insert something manually or create a seeder. To create a seeder, modify database/seeds/DatabaseSeeder.php and add the following to the end of the run() method:
$this->call(OAuthClientsTableSeeder::class);
Now create a file called database/seeds/OAuthClientsTableSeeder.php and enter something like this:
<?php
use Illuminate\Database\Seeder;
class OAuthClientsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
//Add sample users
$oAuthClients = array(
array(
'id' => 'TEST_ENVIRONMENT',
'secret' => 'b17b0ec30dbb6e1726a17972afad008be6a3e4a5',
'name' => 'TEST_ENVIRONMENT'
)
);
foreach ($oAuthClients as $oAuthClient) {
App\OAuthClient::create($oAuthClient);
}
}
}
Run php artisan vendor:publish to publish the package configuration and migrations. Run php artisan migrate to set up the billion-or-so new tables for OAuth. Run php artisan db:seed to seed your database.
You can now set up some test routes in app\Http\routes.php. They should look something like this:
Route::post('oauth/access_token', function() {
return Response::json(Authorizer::issueAccessToken());
});
Route::group(['middleware' => 'oauth'], function () {
Route::get('authroute', function() {
//OAuth will be required to access this route
});
Route::post('postwithauth', function(Request $request) {
$userID = Authorizer::getResourceOwnerId();
$input = $request->input();
return response()->json(array('userID' => $userID, 'input' => $input));
});
});
Route::get('noauthroute', function () {
//No authorization will be required to access this route
});
Pay close attention to the postwithauth route I included above. The OAuth2 package recently changed how you access the user's ID and it took me quite a while to figure out how to get it.
Now that it's time for testing, point your browser to localhost:8000 (or whatever the path is for your test environment) and create a user account for yourself (this step just uses the standard Laravel Auth package).
Go into your HTTP client (I'm currently using Paw and I like it). Go to request->authorization->OAuth2 to set up authorization for the route you're going to test. For Grant Type, select Resource Owner Password Credentials. If you used the seed example I provided above, the Client ID is TEST_ENVIRONMENT, the Client Secret is b17b0ec30dbb6e1726a17972afad008be6a3e4a5, enter the username (email) and password you created through the web Auth interface, your Access Toekn URL will be something like localhost:8000/oauth/access_token (depending on how you set up your test environment), leave Scope blank, and Token should say Bearer. Click on Get Access Token then say Use Access Token when prompted.
That should be it!

The "state" param from the URL and session do not match

In facebook documantion
require('include/facebook/autoload.php'); //SDK directory
$fb = new Facebook\Facebook([
'app_id' => '***********',
'app_secret' => '***********************'
]);
$helper = $fb->getRedirectLoginHelper();
$permissions = ['email', 'public_profile']; // optional
$loginUrl = $helper->getLoginUrl('http://www.meusite.com.br/login-callback.php', $permissions);
When direct it to the url $loginUrl, the return is:
Facebook SDK returned an error: Cross-site request forgery validation failed. The "state" param from the URL and session do not match
I had the same error.
The problem occurred because I did getLoginUrl(...) before getAccessToken()
So rid of getLoginUrl(...) in redirected URL and code should works.
I had the same issue and for me that error was occurring because I did not put session_start(); in my login.php page code before calling getLoginUrl(..) and also at the top of login-callback.php page.
Just put session_start(); in your "login" page and "login-callback" page and it will work surely just like it is working for me now.
There could be 2 reason for this error:
you didn't call session_start(); before getLoginUrl call
You executed getLoginUrl again in login-callback.php, so state value regenerated and mismatched with the redirected value
Possible Fixes : I used the following configuration settings .
Enable WebAuthLogin under the advanced tab . Provide the url in the WebAuthLogin settins as same as that you provide in $loginUrl ;
For example if you use $loginUrl as https://example.com/ use that same in the WebAuthlogin Url
$loginUrl = $helper->getLoginUrl('https://example.com/', $permissions);
This problem occures also in case that you generate 2 or more login links on the same page (e.g. one for login and other for registration - even both point to the same url, they have just different labels).
Facebook SDK creates/updates $_SESSION[FBRLH_state] for each new generated loginURL. So if there are 2 generated URLs (using $helper->getLoginUrl()) then the $_SESSION[FBRLH_state] is 2-times rewritten and valid only for the last generated URL. Previous login URL becomes invalid. It means that it is not possible to generate 2 valid loginURLs. In case that 2 same URLs are generated then return the first one and avoid call of Facebook SDK for generation of second one.
I had the same problem.
The reason for this error is because --->
When "$helper->getLoginUrl" calls, it create a session variable "FB_State", and this is something to FB uses to match the token. Every-time getLoginUrl calls, it create new state. Then after user authorized and redirect back, if you codes cannot detect this event and re-run "$helper->getLoginUrl", then this error will occur.
The solution ->
refine your coding, stop run "$helper->getLoginUrl" again if authorized.
if you already rerun, then set the session variable for the token to NULL if you have, then User can re-authorize again.
when user tries re-authorize, they can remove the authorized APP once or you need to generate new link with "$helper->getReRequestUrl"
Yet, token has be called by "getAccessToken()" before the "$helper->getLoginUrl" or "$helper->getReRequestUrl" runs.
Good Luck!!!!!
Finally, looking into FB code, I discovered that the problem "Cross-site request forgery validation failed. Required param “state” missing" and similars are caused by PHP variable $_SESSION['FBRLH_state'] that for some "strange" reason when FB call the login-callback file.
To solve it I store this variable "FBRLH_state" AFTER the call of function $helper->getLoginUrl(...). Is very important to do only after the call of this function due to is inside this function when the variable $_SESSION['FBRLH_state'] is populated.
Below an example of my code in the login.php:
$uri=$helper->getLoginUrl($uri, $permissions);
foreach ($_SESSION as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
if(!setcookie($k, $v)) {
//what??
} else {
$_COOKIE[$k]=$v;
}
}
}
var_dump($_COOKIE);
And in the login-callback.php before calling all FB code:
foreach ($_COOKIE as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
$_SESSION[$k]=$v;
}
}
Last, but not least, remember also to include code for PHP session so..
if(!session_id()) {
session_start();
}
...
...
...
...
<?php session_write_close() ?>
I hope this response can help you to save 8-10 hours of work :)
Bye, Alex.
This issue was a bit confusing for me, because I had to change a line at the facebook src file:
src/Facebook/Helpers/FacebookRedirectLoginHelper.php
at the function: "validateCsrf" like this:
if ($result !== 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
And change it into:
if ($result === 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
I don't know if this makes a violation to the facebook SDK security, so I truly opened to any exlanation or recommendation for this answer.
You may also make the following changes at the facebook app manager:
add your site and callback-url into your facebook app account at:
setting->advanced:Valid OAuth redirect URIs
Don't forget to add another url with slash (/) at the end of each url and check all 4 checkboxes at Client OAuth Settings.
I had the same error. Are you using 1 file or 2? I was trying to get by using 1 file but my error was resolved when I split into login.php & fb-callback.php as the documentation recommended. My sessions were being re-written so the state was never saved properly.
Good luck!
Happens when the session in missing a needed variable.
might be caused by several things.
In my case I left the "www" out of the callback URL
You could actually be parsing the data from another domain... for example:
website.com is different from www .website.com
If you're parsing data from http ://website.com/login.php to http://www.website.com/fb-callback.php this would be a cross-domain problem and the error you are receiving would be because of that....
http ://website.com and http ://www.website.com are the same but the script identifies them as different..... hope that gives insight to the problem.