Redirect a route in Laravel 5.3 - php

Im building an API in Laravel 5.3. In my routes/api.php file I have 3 endpoints:
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
Several services will call these routes directly, however, when a request comes in from some services, the input data needs to be processed before it can hit these endpoints.
For example, if a request comes in from JIRA, I need to process the input before it hits these endpoints.
Im thinking that the simplest way to do this would be to have a 4th endpoint like the below:
Route::post('/notify/v1/jira', 'Api\Notify\v1\JiraFormatter#formatJiraPost');
The idea being to hit the /notify/v1/jira endpoint, have the formatJiraPost method process the input and then forward the request to /notify/v1/notifications (/alerts, /incidents) as required.
How can I have the /notify/v1/jira endpoint forward the request to the /notify/v1/notifications endpoint?
Do you see a better way to do this?

Depending how your app will work, you could always have your services pointing to /notify/v1/jira and then do the processing like you suggested.
Another alternative is have the JIRA service pointing at the same routes as all the other services but to use a Before middleware group to pre-process your data. Something like
Route::group(['middleware' => ['verifyService']], function () {
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
});
You could check in your middleware your service.
<?php
namespace App\Http\Middleware;
use Closure;
class verifyService
{
public function handle($request, Closure $next)
{
//You could use switch/break case structure
if (isJIRA($request)) {
//Do some processing, it could be outsourced to another class
//$JiraPost = new formatJiraPost($request);
//Keep the requesting going to the routes with processed data
return $next($request);
}
//You could add extra logic to check for more services.
return $next($request);
}
protected function isJIRA(Request $request){
//Logic to check if it is JIRA.
}
}

Related

Laravel 7 User data in __construct

Using Laravel 7.
In the controller constructor, I then hoped to get access to the current user details so I could load main site widgets (buttons links etc) and custom user widgets in to one to be displayed in the view
use Illuminate\Support\Facades\Auth;
...
$widgets = Cache::get("widgets");
$usersdata = Cache::get("userdata");
$this->middleware('auth');
$widgets = array_merge($widgets, $usersdata[Auth::user()->id]["widgets"]);
View::share([
"widgets" => json_encode($widgets)
]);
however at this stage from research the user data is not available (even after authentication ?).
Not sure of best way to access this, or better practice might be to override the middleware auth (where?) so that it could return user id or something eg:
$userid=$this->middleware('auth');
I would like this in the constructor so the same method is in place for all controllers which extend this main controller.
This is intended behavior from laravel, you can read more about it here.
Laravel collects all route specific middlewares first before running
the request through the pipeline, and while collecting the controller
middleware an instance of the controller is created, thus the
constructor is called, however at this point the request isn’t ready
yet.
You can find Taylor's reasoning behind it here:
It’s very bad to use session or auth in your constructor as no request
has happened yet and session and auth are INHERENTLY tied to an HTTP
request. You should receive this request in an actual controller
method which you can call multiple times with multiple different
requests. By forcing your controller to resolve session or auth
information in the constructor you are now forcing your entire
controller to ignore the actual incoming request which can cause
significant problems when testing, etc.
So one solution would be to create a new middleware and then apply it to all routes, something like this, where widgets is your new middleware:
Route::group(['middleware' => ['auth', 'widgets']], function () {
// your routes
});
But if you really want to keep it in the constructor you could implement the following workaround:
class YourController extends Controller
{
public function __construct(Request $request)
{
$this->middleware('auth');
$this->middleware(function ($request, $next) {
$widgets = Cache::get("widgets");
$usersdata = Cache::get("userdata");
$widgets = array_merge($widgets, $usersdata[$request->user()->id]["widgets"]);
View::share([
"widgets" => json_encode($widgets)
]);
return $next($request);
});
}
}

Add Routes and Use Session Information in Service Provider

tl;dr; How can I get session data, then based on authentication, manipulate routes in a laravel ServiceProvider?
The Problem
I need to do two things in a service provider. Register routes and use session data at the same time potentially.
Existing Solutions
Route Without Session Data
I know ServiceProvider::register may happen before RouteProvider::register gets called. ServiceProvider::boot can add routes using something like this...
public function boot(Router $router)
{
$router->prefix('api')->group(function ($router) {
$router->resource('lists', '\App\Extensions\ExtensionName\Http\Controllers\MyController');
});
}
Now this will add the route properly. But by the time that route is accessed, there will be no session data. Therefore Auth::user() will return null. And of course Auth::user() is going to return null in the boot() method as well during the adding of routes.
Session Data Without Route
I could extend the \Illuminate\Session\Middleware\StartSession with my own middleware and fire an event like session.started at the end of an overloaded startSession() method. With this, I should have the session data needed to do something like Auth::user().
public function boot(Router $router)
{
Event::listen('session.started', function () use ($router) {
if (Auth::user()->can('do.something')) {
$router->middleware(['auth'])->prefix('api')->group(function ($router) {
$router->resource('lists', '\App\Extensions\ExtensionName\Http\Controllers\MyController');
});
}
});
}
However, the route will not be added at this point as that stage has already been long over with.
Proper Solution Hints
I've been leaning towards registering my routes, then in my controller, I would somehow inject the session middleware so that it starts the session before my controller code actually runs. But I'm unsure how to do something like that.
It would be very nice if I could have access to the session data before even supplying my routes as it would be cleaner, faster and more secure to include routes that users have access to in the first place instead of checking on every call to them or removing them after they've been added and authorization has been checked.
References
How to retrieve session data in service providers in laravel?
https://laracasts.com/discuss/channels/general-discussion/laravel-5-session-data-is-not-accessible-in-the-app-boot-process
https://github.com/laravel/framework/issues/7906
https://github.com/laravel/framework/pull/7933
https://laravel.io/forum/12-17-2014-session-content-not-available-in-service-providers-register
https://josephsilber.com/posts/2017/01/23/getting-current-user-in-laravel-controller-constructor

Laravel 5 api route

Brief:
Actually, I'm little bit confused while using Laravel api route file.
Question:
If I need to access the data of my web site in other platform (like android app) that is made using laravel, then should I create a different route in api.php file?
If yes then I will be declaring two routes and controllers for each request, first in web.php and second in api.php. Is it correct?
Basically, I want to ask that how I can make an api, so that I can access the data in website as well as in other platforms?
I was searching for a good tutorial for this, but I didn't got a good one.
Ideally the API routes and Web routes should be completely different but if you want it to be same then instead of defining routes in different file you can add routes only in web.php and add a special parameter from your client and in controller if you are getting the parameter then return the JSON Object or else return the view.
Eg.
web.php
Route::get('getUsers','UserController#getUsers');
UserController.php
...
public function getUsers(Request $request)
{
...
if ($request->has('api')) {
return $users; //API Route (Laravel will by Default return the JSON Response no need to do json_encode)
}
return view('pages.user_list'); //Normal Routes hence returning View
}
...
Requests
Normal Request
<Yourdomain>/getUsers
API Request
<Yourdomain>/getUsers?api=true
I hope that helped...
Write your api routes in api.php and web routes in web.php.
The Api routes always have the name api in the routes thus you can differentiate the routes., I mentioned here because as #Akshay Khale mentioned an example with query parameter.
if you want to use the same controller for both API and Web, Api Requests always have the Header Content-Type : Json and "Accept":"application/json" so in your controller you can do it as below.
public function getUsers(Request $request)
{
...
if ($request->wantsJson()) {
return response()->json($users, 200); //here why we are extending response object because using json() method you can send the status code with the response.
}
return view('pages.user_list'); //Normal Routes hence returning View
}
for the laravel 5.6 and above the above answers won't work for me , so here is my 2 cents.
I have put the routes in web.php and api.php and normal no any magic tricks .
public function getUsers(Request $request)
{
....
if( $request->is('api/*')){
...
return response()->json($user_data, 200);
}
...
return view('users', ['users_data'=>$user_data]);
}
It will return json output for
127.0.0.1:8000/api/users
and normal view in html for
127.0.0.1:8000/users

How can we redirect a page to another if a session is not found using route in Laravel 5.3

I am using a session separately other than the default authentication sessions. If an user try to access my secured page, he should have the session set. If anyone without that session try to access means, they will be redirected to error page. I am using Laravel 5.3
The user can view the below two pages only if the session variable named 'secured_user' is set. Otherwise they will be redirect to the error page
Route::get('/secured-page1', 'ValidationController#CheckSecuredLogin_1');
Route::get('/secured-page2', 'ValidationController#CheckSecuredLogin_2');
The best option would be a policy.
You can create certain constrains and couple it with your models. Policies are especially suitable for changing your logic later on.
See here: Create Policy
Within you PagesPolicy, you can add this function:
public function before(User $user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
public function seeSecurePage(User $user)
{
// Your custom Code and session handling
if(session("secured_user")) return true;
return false;
}
and in your controller.
$user->can("seeSecurePage","Pages");
If "can" fails, it will automatically redirect to error 403.
P.S.: Another possibility are Gates
You should use Laravel Middlewares to achieve this, I think middlewares are made for the work you need:
First create a new middleware by running the artisan command:
php artisan make:middleware CheckSesison
Then the CheckSession would look like this:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckSession
{
public function handle($request, Closure $next)
{
if ($session_value != 'YOUR_DESIRED_VALUE') {
return redirect('home');
}
return $next($request);
}
}
Now in your routes file you can use laravel's route middleware() method to implement it like this:
Route::get('/secured-page1', 'ValidationController#CheckSecuredLogin_1')
->middleware(CheckSession::class);
Hope this helps!
In addition to the awnser above, you could also use middleware that's used on the routes and even group them if required. It is a simple, quick and clean solution. Inside the middelware you simple check if the session you require is there and depending on the result you take any action necessary.
Laravel middleware docs

What is the purpose of the authorize method in a Request class in Laravel?

I am today in bit confusion about my website security and some extra code that is written to make the website secure. Below are 2 locations where security is applied.
Inside Route Config, To secure the route, I have used Middleware to check the user role.
Route::group(['middleware' => ['web', 'SuperAdmin', 'auth']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
I mentioned 2 Middlewares.
Auth Middleware : This is for authentication.
SuperAdmin Middleware: This is for Authorization.
Second location is Request class. Here is the code. In authorize method, again same thing is being checked as already done in route
class DepartmentRequest extends Request
{
public function authorize()
{
if(\Auth::user() == null) {
return false;
}
if(\Auth::user()->isSuperAdmin()) {
return true;
}
return false;
}
public function rules()
{
return [
'Department' => 'required',
];
}
}
Question: Should I remove check in Request class? Is that an unwanted validation to secure the request ? As route.config is already doing the job.
What's the use of authorize method? I meant, I am using Request class to validate form inputs. Should I always return true from authorize method?
yes, you should remove that checks in the Request class: if you're already doing that checks in your middleware you should not repeat them
When you specify this:
Route::group(['middleware' => ['web', 'SuperAdmin']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
You're telling laravel that, when it finds a /Create-Department route, it should trigger the handle method of these middleware: ['web', 'SuperAdmin'], before the request is sent to the DepartmentController
So, if you check for authentication and authorization in the middlewares, when the request will get to your controller you're sure that it has satisfied all the middleware it went through
Regarding the purpose of the authorize method: the authorize method is usually used to authorize the actual request basing on some policy you'd like to respect. For example, if you have a request to edit a Post model, in the authorize method you'd check that the specific user trying to edit the post has the permissions to do it (for example being the author of the post )
EDIT
Even if you want to use a middleware for your authorization, it's fine. Anyhow, usually the authorize method within form requests is used to do authorization checks on the specific request.
For instance check this example from the docs :
public function authorize()
{
$postId = $this->route('post');
//here the authorization to edit the post is checked through the Gate facade
return Gate::allows('update', Post::findOrFail($postId));
}
In conclusion: if you're doing your authentication and authorization tasks in middlewares, you don't need to repeat them in the authorize method, but keep in mind that the native purpose of the method is to authorize the specific request

Categories