Laravel \ Socialite \ Two \ InvalidStateException No message Error in Facebook Socialite - php

I am using facebook as the login of my application. After the user successfully logs in, they are redirected to my user homepage. But when I try to refresh the page after log in it throws an error:
Laravel \ Socialite \ Two \ InvalidStateException
No message
This is the code displayed:
public function user()
{
if ($this->hasInvalidState()) {
throw new InvalidStateException; // this line is highlighted
}
$response = $this->getAccessTokenResponse($this->getCode());
$user = $this->mapUserToObject($this->getUserByToken(
$token = Arr::get($response, 'access_token')
));
return $user->setToken($token)
->setRefreshToken(Arr::get($response, 'refresh_token'))
->setExpiresIn(Arr::get($response, 'expires_in'));
}
This is my Controller code:
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Socialite;
class SocialAccountController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Redirect the user to the SNS authentication page.
*
* #return Response
*/
public function redirectToProvider($provider)
{
if ($provider !== 'facebook') {
return abort(404);
}
return Socialite::with($provider)->redirect();
}
public function handleProviderCallback(\App\Models\User $accountService, $provider)
{
try {
$user = Socialite::with($provider)->user();
$create['name'] = $user->getName();
$create['email'] = $user->getEmail();
$create['facebook_id'] = $user->getId();
$user = $accountService->addNew($create);
return view ('user.home')->withDetails($user)->withService($provider);
} catch(Exception $e){
return redirect('/login');
}
}
}
I have found some answers but is not effective on my side. I'm stuck on this for 2 days now. How can I resolve this?

When you get the callback from an OAuth provider (in this case, Facebook) store the data you need into your database and then redirect the user to an account page, or another page that confirms the link was successful.
That callback URL works specifically with credentials that are provided by the OAuth provider. After it is hit the first time those credentials expire and refreshing the page causes the error. By forcefully redirecting the user to another page then they won't be able to refresh and cause the same issue.

Try to refresh the client_secret and run php artisan cache:clear and
composer dump-autoload. It works fine for me.

Related

Laravel Feature testing, how do i test if a profile page has been created with the user?

I created my feature test;
ProfilesControllerTest.php
<?php
namespace Tests\Feature;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class ProfileControllerTest extends TestCase
{
use RefreshDatabase;
/**
* A basic feature test example.
*
* #return void
*/
public function test_the_profile_page_is_rendered()
{
// First The user is created
$user = User::factory()->create();
//act as user
$this->actingAs($user);
// Then we want to make sure a profile page is created
$response = $this->get('/profile/{user}');
//
$response->assertStatus(200);
}
}
web.php
Route::get('/profile/{user}', 'ProfilesController#index')->name('profiles.show');
But it keeps returning an error. I Suspect it is because of the profile link, however I am unsure of how to show it. I have attempted a few variations and i have not managed to get it to work.
I realised factory was not working so instead I tried this;
ProfilesControllerTest.php
public function test_the_profile_page_is_rendered()
{
// First The user is created
$user = User::make([
'name' => 'John Doe',
'username' => 'johnnyd',
'email' => 'johndoe#email.com'
]);
//act as user
$this->actingAs($user);
// Then we want to make sure a profile page is created
$response = $this->get('/profile/{$user}');
$response->assertStatus(200);
}
And I kept getting the error:
Error
php artisan test
PASS Tests\Unit\ExampleTest
✓ basic test
PASS Tests\Unit\UserTest
✓ login form
✓ user duplication
PASS Tests\Feature\ExampleTest
✓ basic test
FAIL Tests\Feature\ProfileControllerTest
✕ the profile page is rendered
Tests: 1 failed, 4 passed, 1 pending
Expected status code 200 but received 404. Failed asserting that 200 is identical to 404.
at tests/Feature/ProfileControllerTest.php:34
30| // Then we want to make sure a profile page is created
31| $response = $this->get('/profile/{$user');
32|
33| //
> 34| $response->assertStatus(200);
35| }
36| }
37|
Profile controller for index was written as follows:
ProfilesController.php
class ProfilesController extends Controller
{
public function index(User $user)
{
$postCount = Cache::remember(
'count.posts.' . $user->id,
now()->addSeconds(30),
function () use ($user) {
return $user->posts->count();
}
);
return view('profiles.index', compact('user', 'postCount'));
}
}
Your first test isn't working because you're attempting to access the wrong URL. You're attempting to go to http://localhost/profile/{user}. That URL is not correct, as there is no user with an id of "{user}". The URL you want to access is http://localhost/profile/1, to see the profile of the user with id 1.
To fix the first test, fix the URL:
// bad
// $response = $this->get('/profile/{user}');
// good
$response = $this->get('/profile/'.$user->id);
Your second test is failing for two reasons:
User::make() will make a new instance of the User model, but it will not persist anything to the database. Since the User won't exist in the database, it does not have a profile URL you can visit.
Again, as in the first test, the profile URL you're trying to visit is wrong.
So, go back to the first test, correct the URL, and you should be good.

Laravel Middleware: Header may not contain more than a single header, new line detected

Laravel's Authenticate middleware gets the path users should be redirected to when they are not unauthenticated, and by default redirects users to /login. I want to implement an added functionality of redirecting the user with a message (such as session time of XYZ mins expired or kindly login to continue). So my Authenticate middleware looks like this:
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Exceptions\HttpResponseException;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
protected function redirectTo($request)
{
if($request->is('api/*'))
{
throw new HttpResponseException(response()->error(['failure_reason'=>'Fresh Access Token Required'], 'Unauthorized Request', 401));
}
if (!$request->expectsJson()) {
// return route('login');
$request->headers->set('Accept', 'application/json');
return redirect("/login")->with("message", "Exceeded an inactivity period of over 15 mins. Kindly re-login to continue");
}
}
}
With or without $request->headers->set('Accept', 'application/json');, I keep getting this error: Header may not contain more than a single header, new line detected. Any ideas on how to resolve this?
With suggestions from #ourmandave and [https://laracasts.com/discuss/channels/laravel/method-redirectto-with-a-flash-message][2], I learnt that the redirectTo() wants to return the redirect route name, not actually redirect. So you should flash the 'message' to your session and then return the redirect '/login'. So I edited my code to look like this below, and it now works:
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Exceptions\HttpResponseException;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
protected function redirectTo($request)
{
if($request->is('api/*'))
{
throw new HttpResponseException(response()->error(['failure_reason'=>'Fresh Access Token Required'], 'Unauthorized Request', 401));
}
if (!$request->expectsJson()) {
session()->flash('message', 'Exceeded an inactivity period of over 15 mins. Kindly re-login to continue');
return route('login');
}
}
}

419 Sorry, your session has expired. Please refresh and try again. custom login isnt working

I have created a login page but I cant get past it because it says Sorry, your session has expired. Please refresh and try again.
This is my controller...
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use DB;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class loginController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$request = Request::all();
$registers = registers::where('employeeID', $request['employeeID'])
->first();
$validCredentials = Hash::check($request['password'], $request-
>get('password'));
if ($validCredentials) {
Session::flash('login','login Successful!');
return view('dashboard');
}
}
this is my route...
Route::get('/', function () {
return view('register');
});
Route::resource('register', 'registerController');
Route::get('login',function(){
return view('login');
});
Route::resource('login', 'loginController');
Route::resource('login', 'loginController#index');
Route::get('dashboard',function(){
return view('dashboard');
});
I dont have a model because I dont think it is necessary
Though your input will be highly appreciated as I am new to laravel
When trying to authenticate user in laravel use the following syntax (you can modify it to what field do you want it to check). What this code does is it will check with your database then if it's a successful attempt, then it will create User session.
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// redirect or do anything else
}
for more detail you can check here : https://laravel.com/docs/5.7/authentication#authenticating-users
=================================
I will try to exmplain your current syntax (part of it)
$validCredentials = Hash::check($request['password'], $request->get('password'));
if ($validCredentials) {
Session::flash('login','login Successful!');
}
here's my short explaination about your code :
$validCredentials.............
only checks if the password is correct with the hash, doesn't make any sessions or cookies. which doesn't truly authenticate the user. it only checks if the password is true
if ($validCredentials) {
Session::flash('login','login Successful!');
}
it only flash session. what you must understand is that flash session is only short term (only available on the next page and will went away if the user change page / refresh the page).
And flash session ONLY WORKS if you create long-term Session (user is trully logged in) using the code like what I wrote above

How to know if user is logged to Facebook? Laravel Socialite

I setup a middleware on a route so that if anyone browses to it, they should be logged to facebook first, if not they'll be redirected to facebook:
Route::get( '/events/facebook', 'EventsController#facebookEvents' )->middleware('CheckFB');
It works fine, however, now the route keeps redirecting back to Facebook over and over.
This is the middleware:
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $provider=null)
{
$provider = 'facebook';
$this->validateProvider($provider);
if ($provider === 'facebook') {
return Socialite::driver($provider)->scopes([
'email',
'public_profile',
'rsvp_event',
'user_birthday',
'user_events',
'user_friends',
])->redirect();
}
return Socialite::driver($provider)->redirect();
}
What I want is that if the user is already logged, he shouldn't be redirected! only the first once.
I tried this:
$user = Socialite::driver('facebook')->user();
But it makes this error:
GuzzleHttp \ Exception \ ClientException (400) Client error: POST
https://graph.facebook.com/v2.10/oauth/access_token resulted in a
400 Bad Request response: {"error":{"message":"This authorization
code has been
used.","type":"OAuthException","code":100,"fbtrace_id":"***
(truncated...)
Im using Auth::check(); to know if an user is logged
use App\User;
use Auth;
use Socialite;
use Redirect;
The method recives as param a $service in this case Facebook
//route to handle the callback
Route::get('/callback/{service}', 'SocialAuthController#handleProviderCallback');
public function handleProviderCallback($service)
{
if(Auth::check())//if user is logged
{
$user_id = Auth::id(); //you get the user ID
$authUser = User::where('id', $user_id)->first(); //you should find the user in the User table
$user_service = $authUser->service; //I saved in the database the service used to log in, so I call it
return view ( 'home' )->withDetails ( $authUser )->withService ( $user_service ); //then I return the view with the details
}else //if user is not login in
{
$user = Socialite::driver( $service )->user();
$authUser = $this->findOrCreateUser($user, $service);//personal method to know if the user is new or it is already saved on DB
Auth::login($authUser, true);//Login if the authUser return is true
return view ( 'home' )->withDetails ( $user )->withService ( $service );
}
Hope it works for you!

Testing unauthorized user restriction in Laravel PHPUnit

Laravel Version 5.2
In my project, users with role_id = 4 has the admin role and can manage users.
I have defined the following ability in AuthServiceProvider:
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('can-manage-users', function ($user)
{
return $user->role_id == 4;
});
}
I have used this ability in the UserController __construct method as follows:
public function __construct()
{
$this->authorize('can-manage-users');
}
In ExampleTest, I have created two tests to check if the defined authorization works.
The first test for admin user who has role_id = 4. This test passes.
public function testAdminCanManageUsers()
{
$user = Auth::loginUsingId(1);
$this->actingAs($user)
->visit('users')
->assertResponseOk();
}
The second test is for another user who does not have role_id = 4. I have tried with response status 401 and 403. But the test is failing:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertResponseStatus(403);
}
First few lines of the failure message is given below:
A request to [http://localhost/users] failed. Received status code [403].
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:196
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:80
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:61
C:\wamp\www\laravel\blog\tests\ExampleTest.php:33
Caused by exception 'Illuminate\Auth\Access\AuthorizationException'
with message 'This action is unauthorized.' in
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Auth\Access\HandlesAuthorization.php:28
I have also tried to use 'see' method as follows:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->see('This action is unauthorized.');
}
But it's failing too. What am I doing wrong? How can I make the test pass?
The mistake is calling the visit method. The visit method is in the InteractsWithPages trait. This method calls the makeRequest method which in turn calls assertPageLoaded method. This method gets the status code returned and if it gets code other than 200, it catches a PHPUnitException and throws an HttpException with the message
"A request to [{$uri}] failed. Received status code [{$status}]."
This is why the test was failing with the above message.
The test can be successfully passed by using get method instead of visit method. For example:
public function testNonAdminCannotManageUsers()
{
$user = App\User::where('role_id', '<>', 4)->first();
$this->actingAs($user)
->get('users')
->assertResponseStatus(403);
}
This test will pass and confirm that a non admin user cannot access the url.
Since the Auth middleware redirects to a login route when unauthenticated by default you could also perform the following test:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertRedirect('login');
}
Since at least Laravel 5.4, you'll want to use the assertStatus(403) method.
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertStatus(403);
}

Categories