DEV Community

Cover image for Proper JSON Responses for Laravel API
Brad Goldsmith
Brad Goldsmith

Posted on

Proper JSON Responses for Laravel API

Last week I wrote about setting up Sanctum within laravel and throughout the post I had said at some point I'm gonna have to figure out to do with unauthorized routes, but why? So if you've ever used laravel middleware Auth or even Auth:Api with passport or JWT auth you have probably at some point hit the route with either an invalid token, or being unauthenticated. The end results of either of these by default is to send you back to a login page to reauthorize. So why would someone become unauthenticated? For many reasons, the token could expire, they could logout, they could login from another device and depending on how you setup that particular token it could remove the previous one. Either way you fall into the same group of "I need to re-authenticate".

In a web app or SPA this is generally as easy as the default behavior by allowing the user to log back in, and then because laravel and it's creators are pretty rad it redirects you by default to the page you were trying to visit. That crew is really smart and I'm sure they already know what to do in my situation but I found that the information I needed wasn't super particularly available.

In the NativeScript-vue app that I am building, returning to the websites login page is just not going to happen. Redirecting to my login screen would be much more appropriate but how do we override the default behavior? I'm using the NativeScript core Http module and I'm using the request method. I think it's a fetch wrapper, if using TypeScript you need to also being in HttpResponse and set the response type to it. But I'm not being fancy with TypeScript even though I probably should have. Anyways back to the point to be consistent throughout the app anytime I use the Http module I expect a JSON return.

Laravel makes this super easy with their API Resources. If you have never looked into these or used them I highly suggest to. It's just a great way to standardize return data and if you're building out an API, these are a must. So since I am expecting a JSON return, redirecting my to a login screen is not the response that I want / expect so I have to override a few things to make it all work for me. This might be the case for you or might not. I feel like as I grow as a developer, the less "standard" I feel. I mean let's be honest there are millions of ways to write "Hello World!" onto a screen but which way is right? Well the answer is whatever way works for you. Are there generally conventions to follow? Well of course there are but what works for someone might not work for you or this particular case.

Before I get off topic again let's go back to the problem of JSON returns in my app. I won't go over the Http call itself as this is not really the point of this post, however I do plan on fully writing up multiple posts on my app build at some point, so please keep up with me if you enjoy this / feel like reading some more nonsense from me. So if you look in app\Exceptions there is a Handler.php. If you are somewhat intuitive you might be thinking this is where Exceptions are handled, and you'd be correct.

    /**
     * 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)
    {
        return parent::render($request, $exception);
    }
Enter fullscreen mode Exit fullscreen mode

This is laravels default render method and normally I would never think of touching something like this (since I honestly don't know exactly what it does) but after reading a few misc StackOverflow posts I was confident enough to mess with something locally and see what happens.

That's another thing, one more super quick piece of advice. Obviously we all know about version control and just how important it is. Whenever I work on something new like this I always make sure to swap branches and break the living heck out of it (not on purpose just usually happens with myself). And if I cant get things back to normal / working I can just wipe that code without it interfering with me.

So as you can see from the render method above it takes in a Throwable type as its second parameter. You can use any of the default PHP Throwable types or any of the available ones inside of the laravel framework. I don't know them all and won't pretend I do but I came across this: \Illuminate\Auth\AuthenticationException in the Laravel API. If you've never looked through these docs they are amazing. Yes there is a lot there but if you have to dig into laravel and the information is not readily available on the standard docs I highly highly highly recommend looking here.

So my ultimate goal of modifying the render method is:

  1. check to see if the Throwable type is an AuthenticationException
  2. make sure this is coming specifically from the APP, otherwise default behavior of returning a login screen is perfectly acceptable.

In comes the beauty of instanceof operator. This checks if an object belongs to a class. So if you've made it this far here is where the magic happens. My new render method:

    /**
     * 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  \Illuminate\Auth\AuthenticationException) {
            if ($request->is('api/*')) {
                return response()->json(['error' => 'unauthorized'], 403);
            }
        }

        return parent::render($request, $exception);
    }
Enter fullscreen mode Exit fullscreen mode

So let's break this down and figure out what all is happening here. Yes there are nested if statements which usually would be a red flag but I think this is a perfectly good use case of it.

Walking through the method we can see that if the $exception passed in as the second type is an instance of \Illuminate\Auth\AuthenticationException we check to see if it in fact came from the APP. How would we know this? Well all of my routes in the app are in my api.php file (and should be in yours too, or if your routes are set up differently you can adjust accordingly) and be default those routes use the prefix /api/. So I'm telling it here if it passes the first check and the request is anything from api/ to return response()->json(['error' => 'unauthorized'], 403); It returns a response with a 403 status code (which is forbidden / refuses to authorize) with the json:

{
    "error": "unauthorized"
}
Enter fullscreen mode Exit fullscreen mode

That is my POSTMAN response and look at that, JSON, woohoo!!!!!!!! We finally have a viable solution for any type of unauthorized request coming from out app. Then in the app if I do get this response I do: this.$navigateTo(Login), and this is some NativeScript code to load back up the Login.vue.

That's about it for the day for me. I'm gonna go play a round of disc golf now and then come back to watch some recaps of round 4 from Worlds, and I guess I'll play around a little with my App since it's kinda overdue. But hey as a small team I'm only one person and I can only do so much. Thanks for reading this and hoping this information was helpful to at least 1 person.

Top comments (0)