Backbone and Laravel API - php

A couple of questions that I cannot seem to find definative answers for, I am working on a RESTful api at the moment, that in time will interact with a handful of client devices, for the time being I am concetrating, on the API, and the web application that goes along with it.
The api lives at http://api.local and the web application lives at http://webapplication.local. The web application is a Laravel installation with a Backbone front-end.
I am trying to save some data from the Backbone to the database of the API. So the API save method looks like this,
public function store()
{
$user_details = Input::all();
$user = new User;
$user->firstname = $user_details['firstname'];
$user->surname = $user_details['surname'];
$user->email = $user_details['email'];
$user->password = Hash::make($user_details['password']);
$user->remember_token = '';
$user->save();
return Response::json($user_details, 200);
}
And it has a route that looks like this,
Route::group(['prefix' => 'api/v1'], function(){
Route::get('users', 'UsersController#index');
Route::get('users/{id}', 'UsersController#get');
Route::post('users/create', 'UsersController#store');
/**
* PUT request to edit the database record
* #todo: Need to be a PUT request in the long run
*/
Route::post('users/update/{id}', 'UsersController#update');
/**
* DELETE request to edit the database record
* #todo: Need to be a DELETE request in the long run
*/
Route::post('users/delete/{id}', 'UsersController#delete');
});
On the front end side (web application) I am saving a model like this,
saveNewUser: function(e) {
e.preventDefault()
var data = this.$el.find('.js-create-new-user').serializeJSON();
// create a new model
var User = new app.User();
User.save(data, {
success: function(model, response){
console.log(model);
console.log(response);
console.log("sucess");
},
error: function(model, response) {
console.log(model);
},
wait:true
});
}
This sends a POST request to the webapplication.local/users/create method, that looks like this,
public function create()
{
$data = Input::all();
$curl = new Curl\Curl();
$curl->post('http://pops.local/api/v1/users/create', $data);
if ($curl->error) {
return Response::json($curl->error, $curl->error_code);
}
else {
return Response::json($curl->response, 200);
}
}
If I look at the $curl->response then I see the data object that is being sent - which I assume means the cURL request is successfully being sent. However nothing gets updated in the API's database?
Am I going about this all wrong? Should Backbone be sending/making requests directly to the API, and leave the PHP behind the web application to do things that the API does not do i.e resize images etc?

You may send requests directly from backbone but you dont have to. Everything looks fine. Check what is returned from $user->save(); by writing something like that:
dd($user->save());
You should get true if everyting is fine, false if there is any error.

Related

How to stop php script in Laravel when Axios request is cancelled?

I'm making a live search using Laravel, VueJs and axios, so every time the user types a new word, the previous request will be cancelled. My problem is that even when I cancel the previous request using the cancel token (https://github.com/axios/axios#cancellation), the php script is still running.
QUESTION: How can i stop the php script if the axios request has been cancelled?
my Vuejs code
fetchData(query) {
if(cancel != undefined)
cancel(); // cancel the previous request
axios.get("http://sample-link", {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
}),
params: {
query : query
}
}).then(response => {
console.log(response);
}).catch(error => {console.log(error.message)})
}
...
my php code
class SearchController extends Controller {
public function Search(Request $request)
{
$query = $request->input('query');
$accounts = Accounts::search($query, null, true, true)->get();
return response()->json($accounts);
}
...
}
I don't know exactly how the Axios cancellation works, but if the client disconnects the HTTP session (as it should) you could try using the ignore_user_abort setting.
public function Search(Request $request)
{
ignore_user_abort(false);
$query = $request->input('query');
$accounts = Accounts::search($query, null, true, true)->get();
return response()->json($accounts);
}
Other useful functions could be:
connection_status: Check the current status of the connection
connection_aborted: Check if the connection has been canceled
I just want to add that your function does not seem very intensive, so I'm not sure how useful such a check is. As Tauqeer suggested in the comment, the usual approach in those situations is to apply a debounce to your javascript search function, in order to only fire the request when the user finished typing.
An example using lodash:
const search = (query) => {
.. your axios request ...
}
const fetchData = _.debounce(search, 300)
Now you can just call fetchData(query) whenever the user types something and it will only send a request when the user stops typing for 300 milliseconds

Laravel API retrieve a single row from database using Vue.js returns blank page

Hi I am trying to get a single row from my database using Vue.js and API call, but I am getting the following error:
GET http://larapp.test/dashboard/api/article/ 404 (Not Found)
For some reason it is not getting the id of the article. I know that api route is returning a single article. I tried this in postman by writing
http://larapp.test/api/article/1
This is returning the row in json format. I am using resources in laravel to transform my model into a json object. This is my routing in api.php.
Route::get('/article/{id}', function ($id) {
$article = Article::findOrFail($id);
return new ArticleResource($article);
});
This is my web.php routing. I dont know if the routing is correct, but when a user goes to /dashboard, the user will be faced with a single article. I have a view where I include the articles tag defined in my app.js and that is working (my vue component is being included in that view). How do i get the id of the user logged in? I am using auth in laravel to handle the authorizaton.
Route::get('/dashboard/{id}', function () {
$id = Auth::id();
$article = Article::find($id);
return view('pages.dashboard')->with('article', $article);
});
This is my Vue.js component. I dont if this is the correct way to list a single article from my DB. I have managed to get it working listing all the rows, but not a single row.
export default {
data() {
return {
articles: [],
article: {
id: '',
title: ''
},
article_id: ''
}
},
created() {
this.getArticle();
},
methods: {
getArticle() {
fetch(`/api/article/${this.article.article_id}`)
.then(res => res.json())
.then(res => {
this.articles = res.data;
})
}
}
}
This is my app.js for anyone wondering.
Vue.component('articles', require('./components/Articles.vue').default);
Vue.component('article', require('./components/Article.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app'
});
Here is my view
#section('content')
<article></article>
#endsection

Refreshing authentication tokens for a Vue.js SPA using Laravel for the backend

I am building a single-page-app with Vue (2.5) using Laravel (5.5) as the backend. Everything works well, except for directly logging in again after having logged out. In this case, the call to /api/user (to retrieve the user's account information and to verify the user's identity once more) fails with a 401 unauthorized (even though the log-in succeeded). As a response, the user is bounced back directly to the login screen (I wrote this measure myself as a reaction to 401 responses).
What does work is to log out, refresh the page with ctrl/cmd+R, and then log in again. The fact that a page refresh fixes my problem, gives me reason to believe that I am not handling refresh of the X-CSRF-TOKEN correctly, or may be forgetting about certain cookies that Laravel uses (as described here ).
This is a snippet of the code of the login form that is executed after a user clicks the login button.
login(){
// Copy the form data
const data = {...this.user};
// If remember is false, don't send the parameter to the server
if(data.remember === false){
delete data.remember;
}
this.authenticating = true;
this.authenticate(data)
.then( this.refreshTokens )
.catch( error => {
this.authenticating = false;
if(error.response && [422, 423].includes(error.response.status) ){
this.validationErrors = error.response.data.errors;
this.showErrorMessage(error.response.data.message);
}else{
this.showErrorMessage(error.message);
}
});
},
refreshTokens(){
return new Promise((resolve, reject) => {
axios.get('/refreshtokens')
.then( response => {
window.Laravel.csrfToken = response.data.csrfToken;
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = response.data.csrfToken;
this.authenticating = false;
this.$router.replace(this.$route.query.redirect || '/');
return resolve(response);
})
.catch( error => {
this.showErrorMessage(error.message);
reject(error);
});
});
},
the authenticate() method is a vuex action, which calls the login endpoint at the laravel side.
The /refreshTokens endpoint simply calls this Laravel controller function that returns the CSRF token of the currently logged-in user:
public function getCsrfToken(){
return ['csrfToken' => csrf_token()];
}
After the tokens have been refetched, the user is redirected to the main page (or another page if supplied)
with this.$router.replace(this.$route.query.redirect || '/'); and there the api/user function is called to check the data of the currently logged in user.
Are there any other measures I should take to make this work, that I am overlooking?
Thanks for any help!
EDIT: 07 Nov 2017
After all the helpful suggestions, I would like to add some information. I am using Passport to authenticate on the Laravel side, and the CreateFreshApiToken middleware is in place.
I have been looking at the cookies set by my app, and in particular the laravel_token which is said to hold the encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. When logging out, the laravel_token cookie is deleted. When logging in again directly afterwards (using axios to send an AJAX post request) no new laravel_token is being set, so that's why it doesn't authenticate the user. I am aware that Laravel doesn't set the cookie on the login POST request, but the GET request to /refreshTokens (which is not guarded) directly afterwards should set the cookie. However, this doesn't appear to be happening.
I have tried increasing the delay between the request to /refreshTokens and the request to /api/user, to maybe give the server some time to get things in order, but to no avail.
For completeness sake, here is my Auth\LoginController that is handling the login request server-side:
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
// $this->middleware('guest')->except('logout');
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(\Illuminate\Http\Request $request)
{
//return $request->only($this->username(), 'password');
return ['email' => $request->{$this->username()}, 'password' => $request->password, 'active' => 1];
}
/**
* The user has been authenticated.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function authenticated(\Illuminate\Http\Request $request, $user)
{
$user->last_login = \Carbon\Carbon::now();
$user->timestamps = false;
$user->save();
$user->timestamps = true;
return (new UserResource($user))->additional(
['permissions' => $user->getUIPermissions()]
);
}
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(\Illuminate\Http\Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
}
}
Considering that you are using an api for authentication, I would suggest using Passport or JWT Authentication to handle authentication tokens.
Finally fixed it!
By returning the UserResource directly in the LoginControllers authenticated method, it is not a valid Laravel Response (but I guess raw JSON data?) so probably things like cookies are not attached. I had to attach a call to response() on the resource and now everything seems to work fine (though I need to do more extensive testing).
So:
protected function authenticated(\Illuminate\Http\Request $request, $user)
{
...
return (new UserResource($user))->additional(
['permissions' => $user->getUIPermissions()]
);
}
becomes
protected function authenticated(\Illuminate\Http\Request $request, $user)
{
...
return (new UserResource($user))->additional(
['permissions' => $user->getUIPermissions()]
)->response(); // Add response to Resource
}
Hurray for the Laravel docs on attributing a section to this:
https://laravel.com/docs/5.5/eloquent-resources#resource-responses
Additionally, the laravel_token is not set by the POST request to login, and the call to refreshCsrfToken() also didn't do the trick, probably because it was protected by the guest middleware.
What worked for me in the end is to perform a dummy call to '/' right after the login function returned (or the promise was fulfilled).
In the end, my login function in the component was as follows:
login(){
// Copy the user object
const data = {...this.user};
// If remember is false, don't send the parameter to the server
if(data.remember === false){
delete data.remember;
}
this.authenticating = true;
this.authenticate(data)
.then( csrf_token => {
window.Laravel.csrfToken = csrf_token;
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = csrf_token;
// Perform a dummy GET request to the site root to obtain the larevel_token cookie
// which is used for authentication. Strangely enough this cookie is not set with the
// POST request to the login function.
axios.get('/')
.then( () => {
this.authenticating = false;
this.$router.replace(this.$route.query.redirect || '/');
})
.catch(e => this.showErrorMessage(e.message));
})
.catch( error => {
this.authenticating = false;
if(error.response && [422, 423].includes(error.response.status) ){
this.validationErrors = error.response.data.errors;
this.showErrorMessage(error.response.data.message);
}else{
this.showErrorMessage(error.message);
}
});
and the authenticate() action in my vuex store is as follows:
authenticate({ dispatch }, data){
return new Promise( (resolve, reject) => {
axios.post(LOGIN, data)
.then( response => {
const {csrf_token, ...user} = response.data;
// Set Vuex state
dispatch('setUser', user );
// Store the user data in local storage
Vue.ls.set('user', user );
return resolve(csrf_token);
})
.catch( error => reject(error) );
});
},
Because I didn't want to make an extra call to refreshTokens in addition to the dummy call to /, I attached the csrf_token to the response of the /login route of the backend:
protected function authenticated(\Illuminate\Http\Request $request, $user)
{
$user->last_login = \Carbon\Carbon::now();
$user->timestamps = false;
$user->save();
$user->timestamps = true;
return (new UserResource($user))->additional([
'permissions' => $user->getUIPermissions(),
'csrf_token' => csrf_token()
])->response();
}
You should use Passports CreateFreshApiToken middleware in your web middleware passport consuming-your-api
web => [...,
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
this attaches attach the right csrftoken() to all your Request headers as request_cookies

Android App interaction with Laravel Action methods

I already have Laravel web pages where i can add/update/delete/Read records from MySQL Database. Laravel version is 5.2.15
Now, I have to integrate Database with Android App. In order to do that I have to post and read Json Data.
Here question is: Should I have 2 public action methods? First for web page that will show records on webpage and second will return json data in Android.
I meant, when I return data to webPage..I will have to write the below code.
return View("View-Path", array("Data" => $Data));
but in case of Android App, I will have to request Json Data.
Please suggest the right approach.
You should develop a simple API to access your APP data from an android client:
Routes
First of all you need to create some specific routes for the API through which you'll serve your data in JSON format
Authentication
The API's routes should handle authentication in a different way in respect on what you're doing now: you can't use the classic session-based approach. Instead you have to use a basic or token-based approach. You've different alternatives, these are some of the most used (from the simplest, to the most complicated )
Laravel HTTP Basic Authentication
Laravel Json Web Token Authentication
Laravel OAUTH2
Data Acess
Once you've setup your routes and authentication, you have to serve your data via the API routes. Since you use the same data in your APP routes and API routes, you can wrap the logic of data building and retrieving in services, and use the services to get the data both in your APP routes and API routes.
Using different controllers for API and APP routes, you have:
//APP Controller method for route: www.app.com/app-route/users
public function getUsers()
{
//wrap the logic to build the data inside the service
$service = App::make('your_service');
//once is ready, get the built data from the service
$data = $service->getData();
return View("View-Path", array("Data" => $data));
}
//API Controller method for route: www.app.com/api/users
public function getUsers()
{
//use the same service to build and get the data you need
$service = App::make('your_service');
$data = $service->getData();
return response()->json( $data );
}
This way you can:
Encapsulate data building and retrieveng in services, so that you don't have the need to duplicate code for data retrieving between APP and API routes
Have different controllers to access APP or API routes; so you can get the data, transform it as you need and serve it to either views or api clients
About the Service class
Regarding the service class i've mentioned, it could be simply one or multiple wrapper classes that you use both in API and APP controllers to build and get the data without repeting code. The structure of such classes depends on how your app work.
For example let's suppose you need to compute some data for each user's project, store it in a variable and then send it to the viev:
public function getUsers($request)
{
$user = Users::with('projects')->find( $request->user_id )->get();
$data = [];
foreach ( $user->projects as $p )
{
//compute some data you need and store it in $data;
}
return View("View-Path", array("Data" => $data));
}
Now if want to make the same thing in the API controller, you'd need to repete this code to get the projects and create the data. To avoid this, you could 'wrap' the data access in a service class, and use the same class in boh controllers:
Service class
public class UserDataBuilder
{
protected $user;
public function setUser( Authenticatable $user )
{
$this->user = $user;
}
public function getData()
{
$user = Users::with('projects')->find( $this-user->id )->get();
$data = [];
foreach ( $user->projects as $p )
{
//compute some data you need and store it in $data;
}
return $data;
}
}
and use the same class in both API and APP controllers:
//APP controller: get the data and pass to the view
public function getUsers($request)
{
$service = App::make( UserDataBuilder::class );
$service->setUser( User::find( $request->user_id )->get() );
return View("View-Path", array("Data" => $service->getData() );
}
//API controller: get the data and convert to JSON
public function getUsers($request)
{
$service = App::make( UserDataBuilder::class );
$service->setUser( User::find(1)->get() );
return response()->json( $data );
}
For android you have to write a separate web service.
You can use one method to do that. Example
public function someMethod(Request $request){
$Data = ['laravel','is','awesome'];
// If the request has the content type is json return a json response
// So you android request will be true for this request
// else return data to webpage
if($request->isJson()){
return response()->json($Data);
}
return View("View-Path", array("Data" => $Data));
}

testing filters in laravel 4

I'm new to laravel. I am trying to test that authentication works for my website and I want to test the authentication process in a test case. I create a in-memory sqlite database, I create a new User and use ->save() method of eloquent to store it in the database. I have setup an authentication filter which checks for the username in the database and depending on that it either allows the user to login or returns "invalid credentials"
// my UserTest.php file :
class UserTest extends TestCase {
public function testUsernameIsNotRequired()
{
// Create a new User
$user = new User;
$user->username = "phil#ipbrown.com";
$user->password = "123456";
//$user->password_confirmation = "password";
// User should save
$this->assertTrue($user->save());
// Save the errors
$password = $user->getAuthPassword();
// There should be 0 error
$this->assertEquals("123456",$password);
$this->seed();
$this->be($user);
$this->assertTrue(Redirect::route('authFilter'));
}
}
just to let you know that the in-memory db is lost once the test is complete as all the connections to it are lost so I want to check that the user that I saved to my db is properly inserted and second I want to check if I can login to my website using the information of this new user.
// my filters.php file :
Route::filter('auth', function()
{
if (Auth::guest()) return Redirect::guest('login');
});
Route::filter('auth.basic', function()
{
return Auth::basic('username');
});
Route::filter('guest', function()
{
if (Auth::check()) return Redirect::to('/');
});
Route::filter('csrf', function()
{
if (Session::token() != Input::get('_token'))
{
throw new Illuminate\Session\TokenMismatchException;
}
});
I was trying to attach a filter to a route so that I can redirect to the route during my test and which calls the auth.basic filter so I can test my filter, I know Im doing a lot of things wrong so please feel free to correct any mistakes that you come accross
my routes.php file :>
Route::get('/', function()
{
return View::make('hello');
});
Route::get('/authtest', array('as'=>'/authtest','before' => 'auth.basic', function()
{
return View::make('hello');
}));
Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), function()
{
Route::resource('url', 'UrlController');
});
Route::get('authFilter', array('as'=>'authFilter','before' => 'auth.basic', function()
{
return Auth::basic('username');
}));
I also have a uri controller which has all the pages for my website
this is what I followed to create my uri controller for the moment
I need a test case that creates a user stores it into the in-memory database and then authenticates using that users information. If any one knows laravel testing for filters please let me know I looked up the documentation for testing filters but I guess it is not well documented.
thank you
Filters are disabled in tests on Laravel 4.
You can use Route::enableFilters() in your test to force the filters on.
You can read up on the various discussions about why/why not to test filters;
https://github.com/laravel/framework/issues/344
https://github.com/laravel/framework/issues/766
http://forums.laravel.io/viewtopic.php?id=7404
https://github.com/laravel/framework/issues/682
You can keep most of this in the unit test.
public function testMyFilter() {
// Set up route with filter.
Route::get('testFilter', ['before' => 'myfilter', function()
{
return 'Hello World';
}]);
// Enable filters.
Route::enableFilters();
// Be a user.
$this->be($this->user);
// Make a request that will use the filter.
$response = $this->client->request('GET', 'testFilter');
// Check the response.
$this->assertResponseOk();
}

Categories