What is middleware in laravel? - php

I'm trying to understand how the middleware works in Laravel. Here's my class can any one explain how does its works.?
<?php
namespace App\Http\Middleware;
use Closure;
class CheckAge
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
}
Thanks

Middleware provide a convenient mechanism for filtering HTTP requests entering your application. For example, Laravel includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.
Reference
Edit: As explained by #num8er
Middleware is the function (or logic) that stands between router and route handler.
In your code:
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
$request->age is a variable that provided in request and can be checked on each HTTP request, if its value <= 200 then user redirects to home route.

As you can see what the middleware is, now lets see the code
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
This code check every request and check the age variable in the request. If the age is less than 200 then the request will be redirect to the home otherwise it will go to the requesting page. Suppose you are requesting /about page but if you can not pass the middleware condition you will be redirected to /home otherwise to /about i.e. given by return $next($request);. Similary works with auth and cors middleware. You can similarly do some check like $request->user->role=='admin' and redirect to admin page or to other page.
return $next($request); this gives you the next requesting route (the original route that have requested)

Middleware provide a convenient mechanism for filtering HTTP requests entering your application. For example, Laravel includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.
Of course, additional middleware can be written to perform a variety of tasks besides authentication. A CORS middleware might be responsible for adding the proper headers to all responses leaving your application. A logging middleware might log all incoming requests to your application.
https://laravel.com/docs/5.4/middleware#introduction
Middleware is a series of wrappers around your application that decorate the requests and the responses in a way that isn't a part of your application logic.
https://mattstauffer.co/blog/laravel-5.0-middleware-filter-style

Middleware main objective is to restrict the unwanted action and here you can check the user given input values and you can allow is valid only.

Related

Laravel redirect away method redirects to own domain

I am coding a Laravel API hosted on api.url.com for an application hosted on www.myurl.com on a server different to the Lravel API using Laravel Fortify.
The problem comes when the user verifies their email on the generated link, they are redirected not to the external application but again back to the API, but on their browser.
THe docs state that this configuration in the Authenticate.pgp Middleware would work:
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return url(env('SPA_URL') . '/dashboard');
}
}
}
Where SPA_URL is set in the .env to www.myurl.com.
This redirects to api.url.com/www.myurl.com/dashboard Obviously causing a 404 response.
I then tried to call an action on a controller as such
public function redirectDashboard(Request $request){
return redirect()->away('www.myurl.com/dashboard');
}
This again redirects to api.url.com/www.myurl.com/dashboard Obviously causing a 404 response.
I have no clue why redirect()->away would still redirect to a url on the same API domain.
Any help would be appreciated, thank you.
The RedirectTo method will redirect to request.
Try to write new middleware
public function handle($request, Closure $next)
{
//check here if the user is authenticated
if ( ! $this->auth->user() )
{
// here you should redirect to another site
return redirect(env('SPA_URL') . '/dashboard');
}
return $next($request);
}
I finally realised what was happening with the redirect: I was using the redirect() helper function rather than the Redirect::class. The helper function appends the current domain to any url or away method call whereas the Redirect::class allows you to redirect to anywhere you need.

Laravel JWT Multi-Page Structure

Its to my knowledge that a JWT based authorization system is usually reserved for SPA'S ( you know, one view, one React/Angular/Vue app, with one bloated app.js file), however I'm attempting to utilize the magic of JWT with a slightly separate structured application.
Structure
Rather than serving up one blade.php view from my Laravel app that garners one Vue app and instance, I'm attempting to serve up TWO separate blade.php views, that each operate as their own separate Vue SPA: one for the exterior of the application (pre-auth) and another for the interior of the app (post-auth).
Current State of App
To power my app's authentication system, I've utilized Tymon's jwt-auth lib ( a beautiful lib btw ) and tie everything together on the front with (as previously stated) Vue/Vuex. Everything works as expected, in my Register and Login components I'm able to hit my api, get a JWT in response, store it locally then annex said token into my Axios headers allowing all subsequent requests to harbor this token.
Dilemma
Now I'm at a crossroads. The post-auth route/view that I want to serve up is protected by a custom JWT middleware that redirects if a valid token is not presented:
Route::get('/home', 'Auth\HomeController#home')->middleware('jwt');
middleware
class JWT
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
JWTAuth::parseToken()->authenticate();
return $next($request);
}
}
and my pre-auth view and all its routes are protected by Laravel's native guest RedirectIfAuthenticated middleware, which is Guarded by JWT now:
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}
Questions
So this begs down to the following questions:
1) after a successful register/login on the front-end and a JWT is generated, stored locally and in Axios headers, How do I then redirect to my post-auth route with this valid token available?
2) How do I then make sure that Valid JWT persist and is present when the guest routes are hit to successfully redirect back to my post-auth route?
I'd prefer to keep all redirects and persistance checks on the backend if feasible
On successful login, you will have the token, let's say its called $jwt_token
You can redirect to the page you are protecting once authorized and set the cookie in the response:
return redirect('/home')->cookie(
'access_token', //name
$jwt_token, //value
config('session.lifetime'), //expiration in minutes (matches laravel)
config('app.url'), // your app url
true // HttpsOnly
);
From here, Axios can have access to the cookie by parsing the cookies on the document and retrieving the access_token
let token = document.cookie.split(';') // get all your cookies
.find(cookie => cookie.includes('access_token')) // take only the one that matches our access_token name
.split('=')[1] // get just the value after =
// terrible code example above for you
Now you can use this in your Axios requests by adding it as the value to Bearer in the Authorization header:
Authorization: `Bearer ${token}`
Your JWT middleware already leverage the authenticate method and therefore it should be handling the expiry for you as it stands:
JWTAuth::parseToken()->authenticate();
Under the hood this will attempt to validate the token's expiry based on the current TTL set in the config/jwt.php file. Given your work flow, I would also blacklist the token if it expires. You can add an Event Listener that listens for expired tokens and blacklists them by listening to Event::listen('tymon.jwt.expired');.
Please excuse any syntax errors, formatting issues or misspellings, I'm on my pone and will edit later to resolve those.
So there are a couple of ways you can make sure the JWT token is available everywhere for Axios or indeed any frontend to use.
The most common way is to store the token in either a cookie or in the Web Storage of the browser (localStorage / sessionStorage)
The difference between localStorage and sessionStorage is that data stored in localStorage persists through browser sessions, sessionStorage is cleared when the page session ends.
The general consensus is that cookies are slightly more secure because they have a smaller attack vector, though neither method is completely secure. If you want to go more in depth you can start by reading this article.
To get more specific concerning your problem, first you want to setup token storage using one of the methods explained above, recommended method is cookies, you can find examples of how to do it with pure Javascript here.
Now that you have the token on every page you can redirect the user whichever way you like. Though I would suggest that instead of using your own middleware for JWT authentication, you can use the one that the JWT library provides: jwt.auth.
This middleware will automatically respond with error codes if something is wrong with the token, if there is it will return one of the following HTTP responses:
token_not_provided
token_expired
token_invalid
user_not_found
If one of these responses are returned (or if the request status code is 400) you can simply use the frontend to redirect the user back to your pre-auth routes.
When signing in, after saving the token to a cookie, use the frontend to redirect to the post-auth routes.
I know you said you wanted to keep redirect logic in the backend but that doesn't really make sense when you're for example calling the API when you're signing in, you can't really both return the token and cause a redirect at the same time from just the backend.
UPDATE
Very simple example of how you can authenticate with only guard and still get a token for the API. Borrowing from the redirect example from #Ohgodwhy, you can put the following inside your RedirectIfAuthenticated middleware.
public function handle($request, Closure $next, $guard = null)
if (Auth::guard($guard)->check()) {
if ((\Cookie::get('access_token') == null)) {
$cookie = \Cookie::make(
'access_token',
\JWTAuth::fromUser(Auth::user()),
config('session.lifetime'),
null,
$request->refeerer,
false, // to make the cookie available in javascript
false // to make the cookie available in javascript
);
return redirect('/home')->cookie($cookie);
} else {
return redirect('/home');
}
}
return $next($request);
}
Just make sure that your $redirectTo in app/Http/Controllers/Auth/LoginController.php is set to a path that implements the RedirectIfAuthenticated middleware.
So here's the logic I ended up implementing:
In my LoginController.php login function, after successful authentication and generation of JWT, I return a response with Json and a new cookie, both with new token passed:
public function login(Request $request)
{
$creds = $request->only(['email', 'password']);
if (!$token = auth()->attempt($creds)) {
return response()->json([
'errors' => [
'root' => 'Incorrect Credentials. Try again'
]
], 401);
}
return $this->respondWithToken($token);
}
protected function respondWithToken($token)
{
return response()->json([
'meta' => [
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60
]
], 200)
->withCookie(cookie('access_token', $token, auth()->factory()->getTTL()));
}
In my guest RedirectIfAuthenticated middleware check for cookie, if exists, setToken which in turn sets Guard to Authenticated and will always redirect to /home if token is available and valid:
public function handle($request, Closure $next, $guard = null)
{
if ($request->hasCookie('access_token')) {
Auth::setToken($request->cookie('access_token'));
}
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
And In my post-auth Routes middleware I also setToken and if its valid and exists, will allow access, otherwise will throw a range of JWT errors which just redirect to pre-auth view:
public function handle($request, Closure $next)
{
JWTAuth::setToken($request->cookie('access_token'))->authenticate();
return $next($request);
}
Finally, I decided to handle redirection in the front-end being I'm using Axios which is promised based and can assure that cookie will be set before redirecting to post-auth view so no funny business happens! Cheers! Hope this helps anyone on their quest to Multi-Page SPA magic!

Laravel 5.6 - check if user can create record based on request parameter

In my app users can create events that are categorised by a related organiser id.
I want to check if the user submitting a request to create a new event has access to the organiser they are creating the event for.
For example:
$organiser_id = $request->input('organiser_id');
if($user->hasOrganiser($organiser_id)) {
// User has permission
}
Obviously the above would work in my controller but ideally I would like to achieve this in my EventPolicy class or perhaps in the EventRequest.
Thanks in advance for your help.
Laravel provides many ways to go through this, you can always check the documentation, in the documentation you will find the checks in the controllers(which you can exclude), the model and in the middleware.
check the authorizing-actions-using-policies
you can always use a middleware which handles the HTTP requests before hitting your app isntance and hence much more control on your app.
Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the Illuminate\Auth\Middleware\Authorize middleware is assigned the can key in your App\Http\Kernel class.
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
Add it to the route file as the link explains so you can check the request and apply your guard.
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
you can check the model way as well in the first link.
I am traveling atm but I think this will help you.
Laravel uses a class called Auth so you can call that class staticky like: Auth::id()
More info:
https://laravel.com/docs/5.6/authentication
Hope this helps.

Laravel Multiple Middleware cause redirect loop

I have a middleware group of auth inside that i want to apply another middleware to one specific route in that view that is if the profile is not completed user cant go to any other route until he complete his profile and submit.
More Specifically, middle ware is causing loop on redirect because i have 2 middleware.
i created middleware with laravel php artisan and checking the user if profile is incomplete he should redirect to the profile/edit page but its not even working with just check on incomplete empty companyname.
Middlware
class incompleteProfile
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(empty(Auth::user()->details['companyname'])){
return redirect()->route('profile');
}
return $next($request);
}
}
Routes File
Routes
Route::group(['middleware'=>['auth'] ], function(){
// User Profile
Route::get('/profile/edit', 'UserController#profile')->name('profile')->middleware('incompleteProfile');
Route::post('/profile/edit', 'UserController#editProfile')->name('editProfile');
If you put that middleware on the profile route ... how can they ever get to the profile route to get the form to update the profile to add the missing information?
You are saying ... if user details for company name are empty, then redirect to profile, but you have that middleware on profile ... so its going to forever redirect to profile because the middleware is telling it to. Your user can never get to profile in this case.
This is the equivalent of assigning the auth middleware to the login page. The auth middleware checks if the user is currently authenticated. Which means if a user is not authenticated they would never be able to get to login, in this scenario, as login requires them to be "logged in".
lagbox answer pretty much says the logic of why it doesn't work.
Try it like this.
Route::group(['middleware'=>['auth'] ], function(){
// User Profile
Route::get('/profile/edit', 'UserController#profile')->name('profile');
Route::group(['middleware'=>['incompleteProfile'] ], function(){
Route::post('/profile/edit', 'UserController#editProfile')->name('editProfile');
//ETC
});
});

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