Laravel Socialite: This authorization code has been used (Facebook) - php

Description: I have implemented the laravel socialite stateless, because I am using Laravel as a backend app with REST APIs and my frontend is in Angular. I get the correct redirect URL, however, when I enter my Facebook credentials and agree to proceed with the application I get redirected to my site with the following issue:
Client error: `POST https://graph.facebook.com/v3.3/oauth/access_token` resulted in a `400 Bad Request` response: {"error":{"message":"This authorization code has been used.","type":"OAuthException","code":100,"error_subcode":36009,"f (truncated...)
Here are the routes in my api.php
Route::get('/auth/redirect/{provider}', [AuthController::class, 'redirectToProvider'])
->where('provider', '[A-Za-z]+');
Route::get('/auth/{provider}/callback', [AuthController::class, 'handleProviderCallback'])
->where('provider', '[A-Za-z]+');
And the following functions in my controller AuthController.php
/**
* #param $provider
* #return JsonResponse
*/
public function redirectToProvider($provider): JsonResponse
{
$response = $this->authService->redirectToProvider($provider);
return response()->json($response);
}
/**
* #param $provider
* #param Request $request
* #return JsonResponse
* #throws \App\Exceptions\Custom\CustomValidationException
*/
public function handleProviderCallback($provider, Request $request): JsonResponse
{
ValidationUtils::validate($request->all(), [
'code' => 'required',
]);
$response = $this->authService->handleProviderCallback($provider);
return response()->json($response);
}
And this is where it resolves in the AuthServiceImpl.php
/**
* #param $provider
* #return array[]
*/
public function redirectToProvider($provider): array
{
if (!in_array($provider, self::PROVIDERS)) {
throw new CustomNotFoundException(trans('errors.not_found.provider'));
}
$success['provider_redirect'] = Socialite::driver($provider)->stateless()->redirect()->getTargetUrl();
return [
'data' => $success
];
}
/**
* #param $provider
* #return array[]|void
*/
public function handleProviderCallback($provider)
{
if (!in_array($provider, self::PROVIDERS)) {
throw new CustomNotFoundException(trans('errors.not_found.provider'));
}
try {
$providerUser = Socialite::driver($provider)->stateless()->user();
if ($providerUser) {
$user = $this->socialAccountsService->findOrCreate($providerUser, $provider);
$user->markEmailAsVerified();
$token = $user->createToken(env('API_AUTH_TOKEN_PASSPORT_SOCIAL'))->accessToken;
return [
'data' => [
'tokens' => [
'token_type' => 'Bearer',
'expires_in' => 5400,
'access_token' => $token
],
'user' => new UserResource($user)
],
'message' => trans('auth.login')
];
}
} catch (\Exception $e) {
throw new CustomUnauthorizedException($e->getMessage());
}
}
You can try it yourself by logging in with Facebook on the following link: https://afillix.common.mk/login

Related

File not uploading when validation with form requests in Laravel

Description
Hi guys,
I have a API endpoint, and im validation incoming data with form requests,
It validated correctly but when i requesting file it says file doesn't exists.
Code
controller method
public function store(StoreRequest $request)
{
$owner = $request->user();
$garage = $owner->garages()->findOrFail($request->garage_id);
$certificate = $garage->certificates()->create($request->validated());
$certificate->addMedia($request->file('image'))->toMediaCollection('certificateImage');
return $this->noContent();
}
FormRequest
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required|filled',
'garage_id' => ['bail', 'required', 'exists:multi-vendor.garages,id', new GarageIsOwn],
'image' => 'required|mimes:jpeg,png,bmp'
];
}
Error
{
"message": "خطای سرور",
"errors": [
"The file \"C:\\xampp\\tmp\\phpAB2C.tmp\" does not exist"
]
}

Laravel routing issues

I'm trying to make a get request to rest api, route bellow:
Route::group(
['middleware' => 'api'],
function() {
Route::get('users', 'UserApiController#index')->name('api.user.list');
Route::get('users/{user}', 'UserApiController#show')->name('api.user.user');
Route::post('users', 'UserApiController#store')->name('api.user.create');
Route::put('users/{user}', 'UserApiController#update')->name('api.user.update');
Route::delete('users/{user}', 'UserApiController#destroy')->name('api.user.delete');
Route::patch('users/{user}/credentials', 'UserApiController#setCredentials')->name('api.user.set_credentials');
Route::get('users/credentials', 'UserApiController#findByCredentials')->name('api.user.find_by.credentials');
Route::get('users/email/{email}', 'UserApiController#findByEmail')->name('api.user.find_by.email');
Route::get('users/phone/{phone}','UserApiController#findByPhone')->name('api.user.find_by.phone');
});
Controller
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreateUserRequest;
use App\Http\Requests\SetCredentialsRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Models\User;
use App\Models\UserEmail;
use App\Models\UserPhone;
use Illuminate\Http\JsonResponse;
use App\Http\Resources\User as UserResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class UserApiController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(): JsonResponse
{
return response()->json(
['data' => UserResource::collection(User::all())],
200
);
}
/**
* Display the specified resource.
*
* #param \App\Models\User $user
* #return \Illuminate\Http\Response
*/
public function show(User $user): JsonResponse
{
return response()->json(
['data' => new UserResource($user)],
200
);
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(CreateUserRequest $request): JsonResponse
{
try {
DB::beginTransaction();
$user = (new User())
->create(
$request->only(['uuid', 'first_name', 'last_name'])
);
$email = $request->get('email');
$user->emails()->save(
new UserEmail(['email' => $email])
);
DB::commit();
} catch (\Throwable $exception) {
DB::rollBack();
return response()
->json(['error' => $exception->getMessage()], 500);
}
return response()->json(
['data' => new UserResource($user)],
201
);
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Models\User $user
* #return \Illuminate\Http\Response
*/
public function update(UpdateUserRequest $request, User $user): JsonResponse
{
$user->update($request->only(['username', 'password', 'first_name', 'last_name']));
return response()->json(
['data' => new UserResource($user)],
200
);
}
/**
* Remove the specified resource from storage.
*
* #param \App\Models\User $user
* #return \Illuminate\Http\Response
*/
public function destroy(User $user): JsonResponse
{
$user->delete();
return response()->json(
null,
204
);
}
public function setCredentials(SetCredentialsRequest $request, User $user)
{
$user->update([
'username' => $request->get('username'),
'password' => \password_hash($request->get('password'), \PASSWORD_BCRYPT),
]);
return response()->json(
['data' => new UserResource($user)],
200
);
}
public function findByCredentials(Request $request)
{
}
public function findByEmail(string $email)
{
$user = UserEmail::all()
->where('email', '=', $email)
->first()
->user()
->getResults();
return response()->json(
['data' => new UserResource($user)],
200
);
}
public function findByPhone(string $phone)
{
$user = UserPhone::all()
->where('phone', '=', $phone)
->first()
->user()
->getResults();
return response()->json(
['data' => new UserResource($user)],
200
);
}
}
Got an error:
No query results for model [App\\Models\\User] credentials
As I understand,laravel is attempting to find credentials field in User model to resolve it.
Controller method is never handled.
If I use Route::post - everything is ok.
How to disable "auto-finding", so I could get control in the controller?
Changing the possion of the route solved the problem:
Route::group(
['middleware' => 'api'],
function() {
Route::get('users/credentials', 'UserApiController#findByCredentials')->name('api.user.find_by.credentials');
Route::get('users', 'UserApiController#index')->name('api.user.list');
Route::get('users/{user}', 'UserApiController#show')->name('api.user.user');
Route::post('users', 'UserApiController#store')->name('api.user.create');
Route::put('users/{user}', 'UserApiController#update')->name('api.user.update');
Route::delete('users/{user}', 'UserApiController#destroy')->name('api.user.delete');
Route::patch('users/{user}/credentials', 'UserApiController#setCredentials')->name('api.user.set_credentials');
Route::get('users/email/{email}', 'UserApiController#findByEmail')->name('api.user.find_by.email');
Route::get('users/phone/{phone}','UserApiController#findByPhone')->name('api.user.find_by.phone');
});

Why http request with Guzzle doesn't work?

I'm developing a website with Laravel 5.7 that has a registration form. When the form is submitted, its params are used to create another user via API.
These API have a .cloudfunctions.net endpoint and the web application is developed using Angular + Firebase.
When I make a GuzzleHttp request to that endpoint, I receive back an HTML response: the google account login page.
The strange thing is that when I run the corresponding cURL command from my vagrant console, or I run the same request with Postman, it returns me a correct json response.
This is a cURL example command:
curl -d '{DATA}' -H "Content-Type: application/json" -u test:test -X POST https://{endpoint}.cloudfunctions.net/api/{function}
And this is my ApiManager class in Laravel project:
namespace App\Utils;
use GuzzleHttp\Client;
/**
* API Manager class.
*/
class ApiManager
{
protected $client;
protected $params;
protected $body;
public function __construct()
{
$this->setupClient();
}
/**
* Setup GuzzleHttp client.
* #return void
*/
private function setupClient()
{
$this->client = new Client([
'base_uri' => env('REMOTE_ENDPOINT'),
'auth' => [env('REMOTE_USERNAME'), env('REMOTE_PASSWORD')],
'headers' => [
'Accept' => 'application/json'
],
'strict' => true
]);
}
/**
* Setup request body as json.
* http://docs.guzzlephp.org/en/stable/request-options.html#json
*
* #param array $params
* #return void
*/
protected function setupBody($params)
{
$this->body = [
'debug' => env('GUZZLE_DEBUG', false),
'json' => $params
];
}
/**
* Decode raw json body to associative array
*
* #param mixed $response
* #return void
*/
protected function getResponseBody($response)
{
$body = json_decode($response->getBody(), true);
if ($body != null && array_key_exists('error', $body)) {
return $body['error'];
}
return $body;
}
/**
* Create user request.
*
* #param array $params
* #return mixed $response
*/
public function createUser($params)
{
$this->setupBody($params);
$response = $this->client->request('POST', '/create-user', $this->body);
if ($response->getStatusCode() == 200) {
return $this->getResponseBody($response);
}
return false;
}
/**
* Update user request.
*
* #param array $params
* #return mixed $response
*/
public function updateUser($params)
{
$this->setupBody($params);
$response = $this->client->request('POST', '/update-user', $this->body);
if ($response->getStatusCode() == 200) {
return $this->getResponseBody($response);
}
return false;
}
}
Anyone can help me find why Guzzle returns the Google account login page, while Postman or cURL from command line work?
Thanks in advance

Laravel 5.5 Validation change format of response when validation fails

In Laravel 5.4, we created a class that all our requests for validation inherited because we needed to customize our response.
class APIRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
/**
* Response on failure
*
* #param array $errors
* #return Response
*/
public function response(array $errors) {
$response = new ResponseObject();
$response->code = ResponseObject::BAD_REQUEST;
$response->status = ResponseObject::FAILED;
foreach ($errors as $item) {
array_push($response->messages, $item);
}
return Response::json($response);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
A sample request that would extend this is shown below
class ResultsGetTermsRequest extends APIRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'school_id' => 'required|integer',
'student_id' => 'required|integer',
];
}
}
And then our sample response on failure would be
{
"status": "FAILED",
"code": "400",
"messages": [
[
"The school id field is required."
],
[
"The student id field is required."
]
],
"result": []
}
However, this doesn't work anymore with Laravel 5.5. I noticed they replaced with response method with failedValidation. This however isn't returning any response when the request isn't validated. If I un-comment the print_r, it is something is returned. It seems the only line that is never executed is the return statement. What am I missing?
public function failedValidation(Validator $validator) {
$errors = (new ValidationException($validator))->errors();
$response = new ResponseObject();
$response->code = ResponseObject::BAD_REQUEST;
$response->status = ResponseObject::FAILED;
foreach ($errors as $item) {
array_push($response->messages, $item);
}
//print_r($response);
return Response::json($response);
}
I guess as per laravel upgrade guide we should return HttpResponseException
protected function failedValidation(Validator $validator)
{
$errors = $validator->errors();
$response = new ResponseObject();
$response->code = ResponseObject::BAD_REQUEST;
$response->status = ResponseObject::FAILED;
foreach ($errors as $item) {
array_push($response->messages, $item);
}
throw new HttpResponseException(response()->json($response));
}
If you want to do this from the FormRequest classes, potentially something like this:
protected function buildResponse($validator)
{
return response->json([
'code' => ResponseObject::BAD_REQUEST,
'status' => ResponseObject::FAILED,
'messages' => $validator->errors()->all(),
]);
}
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator, $this->buildResponse($validator));
}
That would add that response you are building to the validation exception. When the exception handler tries to render this it will check if response was set and if so it will use that response you passed instead of trying to convert the ValidationException to a response itself.
If you want 'ALL' validation exceptions to end up being rendered in this format I might just do this at the exception handler level, as the exception handler already has the ability to convert these exceptions to Json, so you could alter the format in the handler itself and basically not have to make any adjustments to the default FormRequest at all.
If you are in laravel 5+ you can easily achieve this, by overriding the invalid() or invalidJson() method in the App/Exceptions/Handler.php file
In my case, I was developing an API and the api responses should be in a specific format, so I have added the following in the Handler.php file.
/**
* Convert a validation exception into a JSON response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Validation\ValidationException $exception
* #return \Illuminate\Http\JsonResponse
*/
protected function invalidJson($request, ValidationException $exception)
{
return response()->json([
'code' => $exception->status,
'message' => $exception->getMessage(),
'errors' => $this->transformErrors($exception),
], $exception->status);
}
// transform the error messages,
private function transformErrors(ValidationException $exception)
{
$errors = [];
foreach ($exception->errors() as $field => $message) {
$errors[] = [
'field' => $field,
'message' => $message[0],
];
}
return $errors;
}
credit : Origianal Answer

Google Calendar API Refresh accessToken

I'm running into an issue with Google Calendar API.
I can login with google via authCallback but after one hour it gives me a Invalid Credentials error
App\Http\Controllers\gCalendarController.php
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use Google_Client;
use Google_Service_Calendar;
use Google_Service_Calendar_Event;
use Google_Service_Calendar_EventDateTime;
use Illuminate\Http\Request;
class gCalendarController extends Controller
{
protected $client;
public function __construct()
{
$client = new Google_Client();
$client->setAuthConfig('client_secret.json');
$client->addScope(Google_Service_Calendar::CALENDAR);
$guzzleClient = new \GuzzleHttp\Client(array('curl' => array(CURLOPT_SSL_VERIFYPEER => false)));
$client->setHttpClient($guzzleClient);
$this->client = $client;
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$calendarId = 'primary';
$results = $service->events->listEvents($calendarId);
$test = $_SESSION['access_token'];
return view('calendar.tyfuscalender', compact('result', 'test'));
} else {
return redirect()->route('oauthCallback');
}
}
public function oauth()
{
session_start();
$rurl = action('gCalendarController#oauth');
$this->client->setRedirectUri($rurl);
if (!isset($_GET['code'])) {
$auth_url = $this->client->createAuthUrl();
$filtered_url = filter_var($auth_url, FILTER_SANITIZE_URL);
return redirect($filtered_url);
} else {
$this->client->authenticate($_GET['code']);
$_SESSION['access_token'] = $this->client->getAccessToken();
return redirect()->route('cal.index');
}
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
return view('calendar.createEvent');
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
session_start();
$startDateTime = $request->start_date;
$endDateTime = $request->end_date;
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$calendarId = 'primary';
$event = new Google_Service_Calendar_Event([
'summary' => $request->title,
'description' => $request->description,
'start' => ['dateTime' => $startDateTime],
'end' => ['dateTime' => $endDateTime],
'reminders' => ['useDefault' => true],
]);
$results = $service->events->insert($calendarId, $event);
if (!$results) {
return response()->json(['status' => 'error', 'message' => 'Something went wrong']);
}
return response()->json(['status' => 'success', 'message' => 'Event Created']);
} else {
return redirect()->route('oauthCallback');
}
}
/**
* Display the specified resource.
*
* #param $eventId
* #return \Illuminate\Http\Response
* #internal param int $id
*/
public function show($eventId)
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$event = $service->events->get('primary', $eventId);
if (!$event) {
return response()->json(['status' => 'error', 'message' => 'Something went wrong']);
}
return response()->json(['status' => 'success', 'data' => $event]);
} else {
return redirect()->route('oauthCallback');
}
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param $eventId
* #return \Illuminate\Http\Response
* #internal param int $id
*/
public function update(Request $request, $eventId)
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$startDateTime = Carbon::parse($request->start_date)->toRfc3339String();
$eventDuration = 30; //minutes
if ($request->has('end_date')) {
$endDateTime = Carbon::parse($request->end_date)->toRfc3339String();
} else {
$endDateTime = Carbon::parse($request->start_date)->addMinutes($eventDuration)->toRfc3339String();
}
// retrieve the event from the API.
$event = $service->events->get('primary', $eventId);
$event->setSummary($request->title);
$event->setDescription($request->description);
//start time
$start = new Google_Service_Calendar_EventDateTime();
$start->setDateTime($startDateTime);
$event->setStart($start);
//end time
$end = new Google_Service_Calendar_EventDateTime();
$end->setDateTime($endDateTime);
$event->setEnd($end);
$updatedEvent = $service->events->update('primary', $event->getId(), $event);
if (!$updatedEvent) {
return response()->json(['status' => 'error', 'message' => 'Something went wrong']);
}
return response()->json(['status' => 'success', 'data' => $updatedEvent]);
} else {
return redirect()->route('oauthCallback');
}
}
/**
* Remove the specified resource from storage.
*
* #param $eventId
* #return \Illuminate\Http\Response
* #internal param int $id
*/
public function destroy($eventId)
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$service->events->delete('primary', $eventId);
} else {
return redirect()->route('oauthCallback');
}
}
}
This is the error message
Google_Service_Exception in REST.php line 118:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}
I'm thinking it's something with the token that expires, but i have no idea on how to solve it.
Thanks.
I figured out that if i go back to the '/oauth' route, it prompts me to allow google use my google account again.. Instead of refreshing my accesstoken.. Is there a way to make it refresh? without going back to '/oauth'..
Thanks

Categories