I'm recreating some requests that are now written in plain cURL to Laravel's HTTP client. But for some reason it gets in an infinite loop when I try to call a method based on the request response.
This is the call, I have the tokens stored, and when valid everything runs fine. When request returns 401 and want to call the function that creates new tokens and after that retry the original call.
Call:
public function call($path, $methold = null, $payload = null) {
if (empty($this->token)) {
$this->auth_new();
}
$response = Http::withOptions([
'debug' => false
])->withHeaders([
'X-Csrf-Token' => str_replace('csrf_token=', '', Crypt::decryptString($this->token->csrf_token)),
'Cookie' => $cookies,
"Accept" => "*/*",
])->get($url);
if ($response->status() == 401) {
$this->auth_new();
return $this->call_new($path);
} else {
return $response;
}
}
And the auth_new:
public function auth_new() {
$data = json_encode($payload);
$response = Http::withHeaders([
'Content-Type' => 'application/json; charset=UTF-8',
'Connection' => 'keep-alive'
])
->post($this->controller->controller_url . '/api/login', $payload);
$this->token = (response stuff goes here)
}
In cURL it all is working. So when I make a request and it returns a 401, I call the auth function, it stores the new token variables and I return the call.
When I remove the return $this->auth_new() from the call function, it generates new tokens and when I refire the call, I get a valid response. So token wise everything is working. It is just when it returns a 401 and when I want to retry te call automatically it fails with the HTTP client from laravel.
Related
I know this questions have been asked before but non of the answers worked for me.
I am working with React Native and sending API's to Lumen-Backend and i realised that all POST request to LUMEN returns 405 error. Tested it with Postman and it works very fine.
Tried using fetch and axios but they all return 405 errors. Find codes Bellow
Postman request working image
FETCH CALL
const BASE_URL = 'http://192.168.43.232/2019/betbank_api/public/api/';
const url = '/app/auth/login/'
const endPoint = BASE_URL.concat(url);
const data ={ email: 'okeke', password:'passs' }
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
headers: {
'Content-Type': 'application/text'
},
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return await response.text(); // parses JSON response into native JavaScript objects
}
postData(endPoint, { email: 'okeke', password:'passs' })
.then((data) => {
console.log(data); // JSON data parsed by `response.json()` call
alert(data)
});
Also tried implementing the same thing using AXIOS
but ir returns same 405 error. Find Axios code bellow
axios.post(endPoint, data, {
headers: {
'Accept': 'application/json;charset=utf-8',
'Content-Type': 'application/json;charset=utf-8',
}
}).then( (response)=>{
console.log(JSON.stringify(response.data))
alert(JSON.stringify(response.data))
}
).catch( (error)=>{
console.log(error)
alert(error)
})
Find the Lumen Route - API bellow
$router->group(['prefix' => 'api'], function () use ($router) {
$router->post('/app/auth/login', 'AppUserController#postLogin');
});
FInd the method postLogin Bellow
class AppUserController extends Controller
{
protected $jwt;
public function __construct(JWTAuth $jwt)
{
$this->jwt = $jwt;
}
public function postLogin(Request $request)
{
$email = $request->input('email');
$this->validate($request, [
'email' => 'required|email|max:255',
'password' => 'required',
]);
try {
if (! $token = $this->jwt->attempt($request->only('email', 'password'))) {
return response()->json(['status'=>'error','data'=> 'Invalid username and passowrd'], 401);
}
} catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
return response()->json(['token_expired'], 500);
} catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
return response()->json(['token_invalid'], 500);
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
return response()->json(['token_absent' => $e->getMessage()], 500);
}
return response()->json(compact('token'));
}
}
Everythings seems in order but somehow, neither fetch or axios would work when i use the POST method.
But if i change it to GET method, the error stops but the issue would now be how to get the data's been posted from the APP.
QUESTION
Why would all Post request from my App (React Native) be returning 405 from Lumen/Laravel
Here's my code for generating token that will response as a cookie.
public function authenticate(Request $request)
{
// grab credentials from the request
$credentials = ['email'=>$request->header('email'),'password'=>$request->header('password')];
try {
// attempt to verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
return response($token)->cookie(
'token',$token, 60
);
}
And I'm using axios to process it. Here's my code:
axios({
method: 'post',
url: 'http://test.dev/authenticate',
headers : {
'email':'test#gmail.com',
'password':'secret'
},
json:true,
})
axios({
method: 'get',
url: 'http://test.dev/api/users',
json:true,
withCredentials: true
});
Now, I'm having a problem on how to send the token to retrieve users list. I've tried to add withCredentials: true but no luck.
I got a response error token_not_provided.
Questions:
How to get the value of the cookie token from response?
How to send the token using axios with Authorization Bearer {token}?
Thank you In Advance.
You need to add the token to your axios header:
headers: {
'Authorization' : 'Bearer ' + token
}
Make sure you store your token once you receive it back from your initial authorization.
It's necessary to set in your requests header:
withCredentials: true
Then it will allow your, then set the cookie in the 'Authorization' parameter as our friend said in the other answer:
headers: {
Authorization' : 'Bearer ' + CookieToken,
}
My API has been built with Laravel 5.4 Passport, authorisation and issuing access tokens is working properly, but when dealing with a resource like below using Insomnia or Postman:
Authorization: Bearer [encrypted access token goes here which has only "manage-users" scope]
Content-Type: application/json
Accept: application/json
I send above request to this url:
http://apiendpoint.loc/api/users
which has been restricted access to this recourse to tokens which has this scopes
manage-users, test-scope-1
Route::get('/users', [
'as' => 'users',
'uses' => 'UsersController#index'
])->middleware(['auth:api', 'scopes:manage-users,test-scope-1']);
scopes have been defined in the:
AuthServiceProvider
Passport::tokensCan([
'manage-users' => 'testing',
'test-scope-1' => 'test scope',
'test-scope-2' => 'another test scope',
]);
protected $routeMiddleware = [
...,
...,
...,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class
];
The token used to authorize this request has "manage-users" scope only, so I expected to get json response with unauthorized access 401 in addition to the required scope to access this resource which is "test-scope-1".
although I got a HttpException "Invalid scope(s) provided." as HTML response not json
Edit
Auth-Scaffolding is not installed.
After a lot of digging, I found a way to to work around the issue earlier in the exception handler like below:
public function render($request, Exception $exception)
{
// If the request wants JSON (AJAX doesn't always want JSON)
if ($request->wantsJson()) {
if($exception instanceof MissingScopeException){
// Define the response
$response = [
'errors' => 'Sorry, something went wrong.'
];
// If the app is in debug mode
if (config('app.debug')) {
// Add the exception class name, message and stack trace to response
//$response['exception'] = get_class($exception); // Reflection might be better here
$response['message'] = $exception->getMessage();
//$response['trace'] = $exception->getTrace();
}
// Default response of 401
$status = 403;//forbidden
// If this exception is an instance of HttpException
if ($this->isHttpException($exception)) {
// Grab the HTTP status code from the Exception
$status = $exception->getStatusCode();
}
// Return a JSON response with the response array and status code
return response()->json($response, $status);
}
}
return parent::render($request, $exception);
}
so I'll be able to catch the error early and return a json object as a response.
I have this in controller :
public function index(Request $request){
$email = $request->email;
$password = $request->password;
if (!$email || !$password) {return redirect()->back();}
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// Authentication passed...
$this->loggedUser = Auth::user();
if($this->loggedUser){
return response()->json(['isLoggedIn' => true],200);
}
}
return response()->json(['isLoggedIn' => false],200);
}
In angular i have this:
Login (body: Object): Observable<Login[]> {
let bodyString = JSON.stringify(body); // Stringify payload
let options = new RequestOptions({ headers: this.headers }); // Create a request option
return this.http.post('/index', body, options) // ...using post request
.map(response => {return response} ,console.log('aaa')) // ...and calling .json() on the response to return data
.catch((error:any) => Observable.throw(error.json().error || 'Server error' )); //...errors if any
}
Problem is that when i open in browser response i get this:
Deprecated: Automatically populating
$HTTP_RAW_POST_DATA is deprecated and will be removed in a future
version. To avoid this warning set 'always_populate_raw_post_data' to
'-1' in php.ini and use the php://input stream instead. in
Unknown on line 0 Warning: Cannot
modify header information - headers already sent in Unknown on
line 0 {"isLoggedIn":false
}
Any suggestion how can fix that so that in response i get json?
This is a warning you get from PHP5.6 and it will be obscuring your data that you are getting back from your request.
Go into your php.ini file and update this line
;always_populate_raw_post_data = -1
to
always_populate_raw_post_data = -1
Don't forget to restart apache when you have made this update
I'm building a SPA with Vue. My front-end and my back-end (Laravel) are in the same codebase. I want to approach my API (that is in my back-end) via the Laravel Passport Middleware CreateFreshApiToken. I'm approaching my sign in method in my AuthController via web.php.
My problem:
As soon as I'm successfully signed in via my sign in method I would expect that at this time Passport created the laravel_token cookie. This is not the case. The cookie is created after a page refresh. But as I said I'm building a SPA and that's why I don't want to have page refreshes.
What I want:
I want to sign in via my sign in method then use the Passport CreateFreshApiToken middleware. After that I want to use the (just created in the middleware) laravel_token cookie so that I can correctly and safely speak to my own API in my signed-in section of the SPA.
More information:
Kernel.php
// Code...
protected $middlewareGroups = [
'web' => [
// other middlewares...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
];
// Code...
AuthController.php
// Code...
public function login()
{
if (Auth::attempt(['email' => Input::get('email'), 'password' => Input::get('password')], true)) {
return response()->json([
'user' => Auth::user(),
'authenticated' => auth()->check(),
]);
}
return response()->json(['authenticated' => false], 401);
}
// Code...
Login.vue
// Code...
methods: {
login: function (event) {
event.preventDefault();
this.$http.post(BASE_URL + '/login', {
email: this.email,
password: this.password,
})
.then(function (response) {
localStorage.user_id = response.body.user.id;
router.push({
name: 'home'
});
});
},
},
// Code...
What goes wrong? This:
CreateFreshApiToken.php
// Code...
public function handle($request, Closure $next, $guard = null)
{
$this->guard = $guard;
$response = $next($request);
// I'm signed in at this point
if ($this->shouldReceiveFreshToken($request, $response)) { // returns false unless you refresh the page. That's why it won't create the laravel_token cookie
$response->withCookie($this->cookieFactory->make(
$request->user($this->guard)->getKey(), $request->session()->token()
));
}
return $response;
}
protected function shouldReceiveFreshToken($request, $response)
{
// both methods below return false
return $this->requestShouldReceiveFreshToken($request) &&
$this->responseShouldReceiveFreshToken($response);
}
protected function requestShouldReceiveFreshToken($request)
{
// $request->isMethod('GET') - returns false because it's a POST request
// $request->user($this->guard) - returns true as expected
return $request->isMethod('GET') && $request->user($this->guard);
}
protected function responseShouldReceiveFreshToken($response)
{
// $response instanceof Response - returns false
// ! $this->alreadyContainsToken($response) - returns false as expected
return $response instanceof Response &&
! $this->alreadyContainsToken($response);
}
// Code...
I assume it is possible what I want to achieve right? If yes, how?
I had the same issue, decided to stick to client_secret way. I guess it's not relevant for you now, but I've found 2 ways of receiving the laravel token without refresh:
1) sending dummy get request with axios or $http, whatever you use - token will get attached to response;
2) changing requestShouldReceiveFreshToken method in CreateFreshApiToken.php - replace return $request->isMethod('GET') && $request->user($this->guard); with return ($request->isMethod('GET') || $request->isMethod('POST')) && $request->user($this->guard);
function consumeOwnApi($uri, $method = 'GET', $parameters = array())
{
$req = \Illuminate\Http\Request::create($uri, $method, $parameters, $_COOKIE);
$req->headers->set('X-CSRF-TOKEN', app('request')->session()->token());
return app()->handle($req)->getData();
}