32

I am trying to develop a RESTful API with Laravel 5.2. I am stumbled on how to return failed authorization in JSON format. Currently, it is throwing the 403 page error instead of JSON.

Controller: TenantController.php

class TenantController extends Controller
{
    public function show($id)
    {
        $tenant = Tenant::find($id);
        if($tenant == null) return response()->json(['error' => "Invalid tenant ID."],400);
        $this->authorize('show',$tenant);
        return $tenant;
    }
}

Policy: TenantPolicy.php

class TenantPolicy
{
    use HandlesAuthorization;
    public function show(User $user, Tenant $tenant)
    {
        $users = $tenant->users();
        return $tenant->users->contains($user->id);
    }
}

The authorization is currently working fine but it is showing up a 403 forbidden page instead of returning json error. Is it possible to return it as JSON for the 403? And, is it possible to make it global for all failed authorizations (not just in this controller)?

1
  • I would use a 3rd party library like dingo/api. It handled this for you, as well as versioning and transformers. github.com/dingo/api Commented Jul 26, 2016 at 12:29

6 Answers 6

48

We managed to resolve this by modifying the exceptions handler found in App\Exceptions\Handler.php adding it in the render function.

public function render($request, Exception $e)
{
    if ($e instanceof AuthorizationException)
    {
        return response()->json(['error' => 'Not authorized.'],403);
    }
    return parent::render($request, $e);
}
Sign up to request clarification or add additional context in comments.

2 Comments

this doesn't work in Laravel 5.4 dd($e instaceof AuthorizationException) return false.
It still works. Is your exception really an instance of AuthorizationException? Maybe you forgot to import the AuthorizationException class using the "use" operator?
6

Yes, make a simple before method in your policy which will be executed prior to all other authorization checks,

public function before($user, $ability,Request $request)
{
    if (!yourconditiontrue) {
         if ($request->ajax()) {
            return response('Unauthorized.', 401);
        } else {
            return abort('403');
        }
    }
}

2 Comments

Akram, in TenantPolicy.php, it will have a few functions in there such as update and store and it will have different conditional statements. What should I put in the yourconditiontrue then?
whatever your authorization logic to return true false, if its so, then you have to do the conditions on each respective functions , let me refresh my knowledge about Laravel Policy and come back with a solution evening,
6

You can intercept the exception

    try {
        $this->authorize('update', $data);
    } catch (\Exception $e)
    {
        return response()->json(null, 403);
    }

1 Comment

More precisely, catch \Illuminate\Auth\Access\AuthorizationException.
6

As for the latest version of Laravel, as of now version >=7.x,

Generally setting request headers 'Accept' => 'application/json' will tell Laravel that you expect a json response back.

For errors you need to also turn off debugging by setting the APP_DEBUG=false on your .env file, which will make sure the response is json and no stacktrace is provided.

Comments

1

The accepted answer works, but if you don't want to return json for every route you can handle this with middleware.

A brief outline of how to do this:

Create an ApiAuthorization class and extend your main auth class.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Access\AuthorizationException;

class ApiAuthorization extends Authorize
{
    public function handle($request, Closure $next, $ability, ...$models)
    {
        try {
            $this->auth->authenticate();

            $this->gate->authorize($ability, $this->getGateArguments($request, $models));
        } catch (AuthorizationException $e) {
            return response()->json(['error' => 'Not authorized.'],403);
        }

        return $next($request);
    }
}

Add the middleware to $routeMiddleware in App\Http\Kernel.php

'api.can' => \App\Http\Middleware\ApiAuthorization::class,

Update your route. You can now use your new api auth middleware by calling api.can similar to the example in the docs

    Route::get('tenant', [
        'as' => 'api.tenant',
        'uses' => 'TenantController@show'
    ])->middleware('api.can:show,tenant');

This method allows you to return json for specific routes without modifying the global exception handler.

Comments

1

I have also face the same issue in Laravel version 7.3 where the AuthorizationException is not caught. What I come to know that we have to include AuthorizationException in the Handler.php like

<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Access\AuthorizationException;

use Throwable;
use Exception;
use Request;
use Response;

class Handler extends ExceptionHandler
{
    // ...

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $exception
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $exception)
    {
        if ($exception instanceof AuthorizationException)
        {
            return response()->json(['message' => 'Forbidden'], 403);
        }
        if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
            return response()->json(['message' => 'resource not found')], 404);
        }
        
        return parent::render($request, $exception);
    }

    // ...
}

FYI if you just add the AuthorizationException by using the following statement

use AuthorizationException;

It still not working. So we have to specify the fully qualified namespace path.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.