Laravel automaticly redirect on authentication exception - php

im working with laravel 5.5 and tried to protect an api route. I assigned the 'auth' middleware to this route, but when i tested it, i get an InvalidArgumentException aka 'Route [login] is not defined'. Im not reffering to this route in any way, laravel automaticaly tried to redirect to this route. I found the following code line in file 'laravel\framework\src\Illuminate\Foundation\Exceptions\Handler.php':
/*
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(route('login'));
}
So im wondering, whats the best way to catch this exception globally on every route?

From my understanding, its because you added an auth middleware to the route. So whenever the route is accessed without an authentication, the route is redirected to the name login route which is the login page by default.
You can add an unauthenticated method to your app/Exceptions/Handler.php with the following code. So if the request is in API or expecting JSON response, it will give a 401 status code with JSON response. If the route is accessed using web routes, it will be redirected to the default page. In the below example, am redirecting it to a login page.
Note: This method existed in the app/Exceptions/Handler.php till laravel 5.4.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(('login'));
}
Don't forget to import use Illuminate\Auth\AuthenticationException;
Edit: Explanation for the error Route [login] is not defined. This is because laravel is trying to redirect to a named route. This named route is available if you are using the laravel auth scaffolding. If you are manually creating the functions, even if the route login exists, it should be named as login. For more information, see https://laravel.com/docs/5.5/routing#named-routes

Related

Laravel 8 route login not found with api 401 errors

Am using laravel 8 and also using the auth:api middleware in my api routes. In my request url am setting the correct bearer token with Bearer token with the token been generated by passport. This works well but whenever a request has an expired token it throws a Route [login] not defined error
I have the following in my api routes
Route::middleware("auth:api")->group(function (){
Route::resource("users",UserController::class);
});
I have also added the following to my app\Exceptions\Handler.php
class Handler extends ExceptionHandler
{
public function report(Throwable $exception)
{
if ($exception instanceof \League\OAuth2\Server\Exception\OAuthServerException && $exception->getCode() == 9) {
return response('unauthorized', 401);
}
parent::report($exception);
}
}
But the above still doesnt work. What do i need to add to make this work.
This is happening because Laravel is probably throwing AuthenticationException exception. That logic is defined in the the default Illuminate/Foundation/Exceptions/Handler.php in the method unauthenticated():
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}
As you can see, in this default example, if your API excepts a JSON response, you would get JSON response with 401 HTTP code. If that's not the case, you would be redirected to the login (hence the error, because you don't have the login route defined)
So, in order to solve this, you can do two things.
The first one is to override this method and actually write your own condition and responses. You can do that by just defining unauthenticated() method in your custom Handler.php. You could do something like this:
protected function unauthenticated($request, AuthenticationException $exception)
{
//This will return just a plain 401 page
return response('Unauthenticated.', 401);
}
But, since this is probably an API, it would be better to use the second solution, which would be to just include Accept: application/json in your request header. This tells your API that you are sending a JSON request. In this case, you don't need to override the unauthenticated(), because this exception will be caught in the following condition, from the default Laravel handler:
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
When this block of code is triggered, you will get a JSON response with 401 HTTP code, which is the expected one for the API's. If you need any more help, let me know.

Retain route for respective guard logins after logout via address bar

I've got multiple guards. All are working fine.
However if a role log out (unauthenticated) from the app and I want to access any e.g /admin/* except admin/login page via address bar, it doesn't redirecting and retain the /admin/login route. it's redirecting to /login instead. It applies to other roles too. How do I do to retain it?
If you are using the default built-in \Illuminate\Auth\Middleware\Authenticate middleware to handle authentication then that will throw an AuthenticationException along with the guards that were checked. You can handle the exception differently by overriding the unauthenticated in your exception handler:
App/Exceptions/Handler.php
class Handler extends ExceptionHandler {
//...Other code
protected function unauthenticated($request, AuthenticationException $exception) {
if (in_array('admin', $exception->guards()) && !$request->expectsJson()) {
return Redirect::guest('/admin/login');
}
return parent::unauthenticated($request, $exception);
}
corrected by OP

Laravel two 404 styles

I have a main site and an admin control panel.
I want to have different 404 pages for each version.
How should I do this? I currently have the following code in my app/Exceptions/Handles.php file:
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if($exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException)
{
$view = $request->is('admin/*') ? 'acp.errors.404' : 'errors.404' ;
return response()->view($view, [], 404);
}
return parent::render($request, $exception);
}
But I use the package spatie/laravel-permission and get the following error;
Trying to get property 'role' of non-object (View: F:\Development\RPR\site\resources\views\layouts\acp.blade.php) (View: F:\Development\RPR\site\resources\views\layouts\acp.blade.php)
I use in acp.blade.php auth()->user()->role, to get the user role, which just works fine without any exception. How should I fix this?
Here are two ways to accomplish different 404 views depending on the route. Both will allow you to have these error pages:
/resources/views/acp/errors/404.blade.php
/resources/views/errors/404.blade.php
The directories will be checked in order until a view is found, which means you can selectively add custom error views and fall through to the default when none exist. If the route did not match, then it will not look for a custom error page.
Option 1
Override registerErrorViewPaths() inside app/Exceptions/Handler.php:
/**
* Register the error template hint paths.
*
* #return void
*/
protected function registerErrorViewPaths()
{
parent::registerErrorViewPaths();
if (request()->is('admin/*')) {
View::prependNamespace(
'errors',
realpath(base_path('resources/views/acp/errors'))
);
}
}
Option 2
Create a ViewServiceProvider:
php artisan make:provider ViewServiceProvider
Register your provider in config/app.php:
'providers' => [
// ...
App\Providers\ViewServiceProvider::class,
],
Edit the boot method of your provider:
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if (request()->is('admin/*')) {
View::prependNamespace(
'errors',
realpath(base_path('resources/views/acp/errors'))
);
}
}
For the second part of the question, auth()->user() is only available when the session middleware has run. If the 404 was caused by the route not existing, then the request does not go through the web middleware and unfortunately sessions and auth information will not be available. However, if the 404 was caused by a ModelNotFoundException triggered inside a controller, then the web middleware probably did run and you can access the user.
Inside your error view you have to check if the user is signed in:
#guest
<p>Hello, guest</p>
#else
<p>Hello, {{ auth()->user()->name }}</p>
#endguest
If this is not good enough for your use case, then you might want to try Route::fallback(), which allows you to define a controller for serving 404 pages and does run web middleware.

Laravel - adding VerifyCsrfToken to controller getting ajax request

I am creating a controller that receive an AJAX request and from Laravel documentation, i can send header with X-Csrf token
https://laravel.com/docs/5.5/csrf#csrf-x-csrf-token
On my Controller i have something like this :
public function checkPromotion(Request $request)
{
try {
$this->middleware('VerifyCsrfToken');
}
catch (TokenMismatchException $e){
return response()->json(['error' => 'Error Token Provided']);
}
}
}
When i tried and sent a blank post request to this controller , the respond was blank .
There are three issues here:
If you are going to add middleware in a controller, you must do so in the constructor.
Out of the box, you cannot handle middleware exceptions in a controller action. You'll need to handle the exception in your \App\Exceptions\Handler class. The handle method of that class will receive the TokenMismatchException when token verification fails.
The string 'VerifyCsrfToken' is not a valid way to reference your middleware.
Regarding #3, the middleware method takes the name of a middleware group, or the FQCN of a particular middleware.
The following should work:
$this->middleware(\App\Http\Middleware\VerifyCsrfToken::class)
(I'm assuming that you are using the default App namespace)
If you get a "Session store not set on request" exception, it's because cannot use CSRF middleware without the StartSession middleware.
Most likely what you really want is the web middleware:
$this->middleware('web')
This will include the CSRF middleware, the session start middleware, and a few others (see your http kernel for details).
If needed you can exclude routes from CSRF verification by using the $except array in your VerifyCsrfToken class
The middleware method on Controller is registering a middleware in an array on the controller. That middleware is not ran at that point.
When this method is called in a constructor the router/route collection will have access to the getMiddleware method after the Controller has been resolved to be able to build the middleware stack needed for the current route/action.
You may want to be dealing with this exception in your exception handler, App\Exceptions\Handler.
Laravel 5.5 Docs - Errors - The Exception Handler - Render Method
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Session\TokenMismatchException) {
return response()->json(['error' => 'Error Token Provided']);
}
return parent::render($request, $exception);
}
protected function tokensMatch($request)
{
// If request is an ajax request, then check to see if token matches token provider in
// the header. This way, we can use CSRF protection in ajax requests also.
$token = $request->ajax() ? $request->header('X-CSRF-Token') : $request->input('_token');
return $request->session()->token() == $token;
}
Add this function inside VerifyCsrfToken.php

Laravel Auth hidden redirect

Install new Laravel 5.4 project.
Run php artisan make:auth.
Go to http://localhost:8000/home (without being logged in).
I get redirected to http://localhost:8000/login
This redirect seems like magic. This is the route for home:
|| GET|HEAD |home||App\Http\Controllers\HomeController#index|web,auth|
We never get to the index() method because the auth middleware takes
over. I open the auth middleware file Illuminate\Auth\Middleware\Authenticate.php and there's no mention of the /login redirect.
Where is the redirect from /home to /login defined?
Check app/Exceptions/Handler.php file and you'll see Exception handler for unauthenticated users:
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}
P.S. redirect to home you can see in app/Http/Middleware/RedirectIfAuthenticated.php file.
UPD1: redirect to home after login/register you can set in app/Http/Controllers/Auth/LoginController.php and app/Http/Controllers/Auth/RegisterController.php.
It looks like
protected $redirectTo = '/home';

Categories