Protecting API endpoints of a public contact formular - laravel

I'd like to do the following:
My website is designed as Vue SPA, which performs requests to an Laravel-powered API. This API includes a POST-Route that allows to submit the content of a contact formular.
/*Contact Routes*/
Route::group(['prefix' => 'contact'], function ($router) {
/*Send*/
Route::post('/send', 'ContactApiController#send')
->name('api.contact.send');
});
Now I'd like to protect this route, so only requests that are submitted from the SPA are allowed, otherwise I guess I'd need to handle a lot of spam once somebody figures out that it's enough to create simple post requests to the API endpoint.
There's no user auth planned on that site at the moment, so I think that Laravel airlock does not work in this scenario.
My question: How can I protect my routes from being accessed externally?

Related

How to log in from a single page react app to Laravel 7.x on another domain?

I have a single page create-react-app running on localhost:3000 and I want to log in to a laravel 7.x instance running on myapp.loc (vhost).
Eventually I would like a single page running on app.mysite.com with laravel running on api.mysite.com.
I'm able to log in to my laravel instance directly from myapp.loc. I've installed Laravel passport and the scaffolding, etc and can create Client IDs and Secrets, but I'm unsure if they are needed and if so, how to use them.
What I am unsure of and cannot find any documentation for, is how to log in to laraval from my SPA (running on localhost:3000). I have set my CORS headers and can connect requests that don't require auth, but I don't know how to log in or structure auth requests once logged in.
I can't find any documentation on axios.post / get requests with a focus on logging in from another domain and maintain user-based access.
Since I don't know enough to ask a concise question, there are three layers that I feel like I should be searching for an answer.
Is it possible for laravel to act as the backend for my single page app from another domain?
If so, are there documented, best practices / well worn paths for accomplishing this?
If so, what would a sample axios login and subsequent session call look like? (e.g. payload and header shape)
Yes you can, I suggest to use https://laravel.com/docs/7.x/sanctum instead of passport because is easier to setup and it was created especially for this scenario.
You need to configure CORS using the official Laravel Package https://github.com/fruitcake/laravel-cors this way you will open Laravel's CORS to be able to reach it from anywhere localhost, or any domain that you can set up into allowed_origins. inside the cors.php config file according to the documentation of the package.
After configuring Sanctum/Passport and ensuring you are generating the required token for your SPA using the createToken method described in Sanctum or Passport docs, you have to save the token to connect to your protected endpoints however they recommend to use cookie SPA based authentication but that's not strictly necessary.
Create an API Service
In this example I will create an API Service to encapsulate API calls
import axios from 'axios';
const URI = 'https://yourlaravel.api/api/';
axios.defaults.headers.common = { Accept: 'application/json', 'Content-Type': 'application/json' };
const ApiInstance = axios.create();
const API = {
login: (user) => {
return ApiInstance.post(`${URI}login`, user);
},
getUser: () => {
return ApiInstance.get(`${URI}user`);
},
setUser: (user) => {
return ApiInstance.post(`${URI}user`, user);
},
};
Send A Login Request to your login endpoint and save the token
import API;
API.login({email:'mail#domain.com',password:'32323'})
.then(response=>{
//save the token
//response.data.accessToken
})
Fetch data from your protected endpoints using the token
//Set the default authorization header when you have an access token
axios.defaults.headers.common = {'Authorization': `Bearer ${token}`}
//Get your data
API.getUser().then(response=>{
//response.data
})
//Post something
API.setUser({user:'Os'}).then(response=>{
//response.data
})
All those things are possible, you just need to set up cors and you are good to go. For auth you can use passport or your own custom app key setup, it all depends on what you are trying to achieve. I suggest reading up about RESTfull apis, that would be a good start.
In order to perform a handshake between FE and BE on FE you would have a login form submission of which will send e request to BE (backend api) and if login is success you send back a key which then FE should store. Any future requests from FE should append that key in the header to gain access to authorised areas.
There should be plenty of information on this subject (RESTfull Api & Token authentication) on google.

Laravel AJAX requests via API Controller / Passport

Most of the requests like ChangePass, Create Blog, Update Blog and etc. are done via AJAX. I've decided to make API controllers that handle every AJAX based request that the user is sending.
Should I use Passport as well because of the API calls?
What's the best way to authorize every registered/logged user to make AJAX based requests, without they have to authorize themselves manually?
If you make those calls from a page which is already authenticated with Laravel adding Passport is not needed, just add csrf token as documentation explains, https://laravel.com/docs/5.7/csrf#csrf-x-csrf-token.
Passport is needed if your site doesn't authenticate against Laravel, like if you have separate NodeJS based client site and Laravel is acting only as an API backend.

Laravel Web Route or API Routes for application with VueJS in front end

We're currently developing a multipage app using VueJS as frontend javascript framework, Element.io as CSS framework.
We're not exposing any web services or some kind.
Our application is responsive - users can create records using desktop and mobile.
Do I need to create API routes or WEB routes is sufficient enough?
Are there any scenario you can think of that I need an API route?
Web routes are for frontend views where API routes would be for API calls, you would definitely need to separate them as your VueJS will make calls to your API with JSON and get a JSON response in return with error codes to handle your errors efficiently.
Web Controller:
return view('blade_file')->with(compact('var1', 'var2'));
If you set the error codes here, it will show you the blade file for that error code, eg. 404 will show you the blade view file at ./resources/views/errors/404.blade.php but your application will expect JSON response instead of HTML response.
API Controller:
return response()->json(compact('var1', 'var2'), 200); // success
return response()->json(['error' => 'bad request'], 400); // bad request
If you set error codes here, you will still get a JSON response, just with the error code specified.
Conclusion:
Separate your frontend and backend with API and Web routes as requests/responses are handled differently.
Notes:
Remember to add your CSRF token in your header when making ajax/axios requests to this API.
Make sure your middleware is api. If the API only allow authenticated users, you would need the middleware to be auth:api and you would need to use Laravel Passport.
Remember to add the namespace of Api to your API routes, either in routes/api.php file or app/Providers/RouteServiceProvider.php.

How to authenticate API requests in Laravel?

I am currently building some sort of posts based web application using Laravel 5(.4). I have decided to load asynchronously the comment section for each post(and refresh it periodically). After some research I have decided to write a small integrated REST API (using the api routes of Laravel) that should answer to the requests made through AJAX.
However, I am facing the problem if authenticating the incoming requests. Take for example a request to post some comment. How exactly would you recommend to do that?
If you are making AJAX requests from browser and you are signed in then you don't need to use Laravel Passport tokens.
You can define certain routes which will be using web,auth middleware on requests like webapi/comments/get like this.
Route::group(['middleware' => ['web','auth]], function () {
Route::get('webapi/comments/get', 'CommentsController#get');
}
And use Auth Facade as you do in web request i.e Auth::check(), Auth::user() etc. and return the data in JSON like this.
class CommentsController extends Controller
{
public function get(Request $request)
{
if($request->acceptsJson()){
$data = array();
// add data
return response()->json([
"data"=> $data,
"status" => true
]);
}else{
return abort(404);
}
}
}
You can also send Accept header in AJAX request as application/json and in controller check if request $request->acceptsJson() and make your decision to show content when url is loaded from browser address bar or requested as AJAX.
Laravel Passport token are useful where there is no session and cookies are managed.
hope this helps :)
"Passport includes an authentication guard that will validate access tokens on incoming requests. Once you have configured the api guard to use the passport driver, you only need to specify the auth:api middleware on any routes that require a valid access token" - from the Laraven Documentation.
Apparently I have to configure passport, and after that configure the auth:api middleware to use the passport driver. Correct me if I'm wrong, please :)

Authorization Policies/Gates for Laravel 5.3 web app consuming own API w/ Passport

Using Laravel 5.3 I've set up a web app that consumes its own API. Authentication successfully handled by Passport. Web app uses auth middleware in routes and Model Policies for authorization. API routing uses default 'auth:api' token guard to control access.
I would like to use the same Policies in app/Policies for API authorization as well as the web auth, but I don't understand how. Calls such as $this->authorize('view', $model) do not work. I guess I need to pass the user from Auth::guard('api')->user() to the Policies somehow?
Any help would be appreciated!
Update: Got it working.
Seems that even for the API calls Laravel was still using the user from the web guard to check against policies. This user is undefined for API calls. So I needed to tell Laravel that all API calls should use the api guard.
Create a new middleware with Auth::shouldUse('api'); in the handle function.
Assign the middleware to the api section in the kernel.
Laravel will now use the api guard for all API requests. Calls like $this->authorize('view', $model) will work in both web and api.
Update: Got it working.
Seems that even for the API calls Laravel was still using the user from the web guard to check against policies. This user is undefined for API calls. So I needed to tell Laravel that all API calls should use the api guard.
Create a new middleware with Auth::shouldUse('api'); in the handle function.
Assign the middleware to the api section in the kernel.
Laravel will now use the api guard for all API requests. Calls like $this->authorize('view', $model) will work in both web and api.
Just use auth:api middleware for routes with Policies

Resources