Laravel Signed URLs
Tags: laravel, signedurl, php
Published: 2021-07-31 11:18:34

So this week has been a major "let me down" at work.  I've been at the point to where I the NativeScript app that I've been building for the past few weeks is ready to be bundled up and put in the app store / google play store for some internal beta testing.  Well the people in charge of the credentials and whatnot at my company waited to long and we don't even have access to our Apple Developer Console at this point.  Google is still verifying a bunch of things for us, and basically all of this administrative stuff should have been taken care of a long time ago.  It puts me at a point where once we finally have everyone on our side they are gonna say well Brad how long now and Brad we need this ASAP.  Y'all slacked on your end so give me a break, but let's be honest, they don't care about that they just care about getting their app in the store.  

So I had a bit of downtime this week to work on some misc items on our laravel monolith.  One thing that was requested by management was to make single landing page for captains for a booking to they could manage their booking without having to sign in to the web app itself.  We built a feature for customers a few months back that allowed a customer to receive a signed url so that they did not need to create an account and could merely check out as a guest, which seems pretty standard nowadays on most sites in which you can purchase items.  So the flow of a booking basically goes like this:
1. customer completes a checkout and a booking is created in the DB with a status of pending.
2. customer is notified via email that the captain has 24 hours to respond (decline / accept)
3. captain is notified via email / text about the booking with a link to accept / decline
4. 6 / 12 / 18 / 23 hours after request they get an email / text reminder (unless the booking has already been accepted / declined) with the same accept / decline link
5. 24 hours after a booking was requested and not accepted / declined, captains get an email notifying them that the booking has been automatically declined and is no longer available to them / the customer.

When a captain clicks on their link they need to log into their account to manage their booking, and this is what they see before hand (login screen) 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh79mb0xj9o1gwz1g9iu.png)
If they forget their password you can see there is a link and that takes you here:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4yr2rq2yasukclj4inlm.png)
So I cannot make this up when I tell you that the admin staff keeps telling me that captains cannot figure out how to reset their passwords and or they do not know how to login.  Is the user base just too old or is this not intuitive enough?  Honestly at this point I don't care, but my major concern is that for the past month I've been working on a mobile app and if we are having captains / users that cannot figure out how to login to that, how in the heck are they going to use, let alone log in to a mobile application?  I think it might be time for me to update my resume and start looking for a new gig.  Expectations are so high and at this point I truly feel like I'm set up for failure.  But hey the paychecks keep hitting my account so time to keep trecking on.  

So finally the point of this post, sorry just venting a little since well I don't have a whole lot of people to vent to, so I use this community for a tad more than just reading / posting.  But anyways, so I was asked to build out a single page for where a captain could manage their booking.  All of the links in the email / texts would default to this landing page as well as create a laravel Nova action to resend the link to them.  Obviously it can't be public route where anyone could access but also they need not login to access it, so in comes laravel's signed urls.  A signed URL has  a "signature" hash appended to the query string which allows Laravel to verify that the URL has not been modified since it was created. Signed URLs are especially useful for routes that are publicly accessible yet need a layer of protection against URL manipulation (taken directly from laravels documentation).  Signed URLs can be set to expire but I chose to let it run forever since I don't want to have to create multiple URLs for a captain to handle (one during the 24 hour pending period, one after it's been accepted / declined).  

First things first this page has a little interactivity to it since a captain has the ability to accept / decline, so I created a livewire component for the page.  I wont go into too much detail about it since I've written a few posts about it but basically it allows us backend developers to write php on the front end and keep it reactive!!!!!!  Cool I know, but will it be around in 5 years, hell I might not be so again not my problem at this point.  One cool thing about URL signatures is that laravel has built in middleware to check for a valid signature.  You can add it on to any route `->middleware('signed')` or you can access it in a controller if you'd like as well: 
```
if (! $request->hasValidSignature()) {
        abort(401);
    }
``` or even directly in closure on the route but that's all up to you.  I chose to check it on the route itself like this:
```
Route::get('/someting_here/somethings/{something}', \App\Http\Livewire\Web\CaptainSingleBooking::class)->middleware('signed')->name('catpain.bookings.itenerary');
```

And then on my `User` model I have to create 2 methods, one for creating the signed URL (notifications) and one for sending the URL (nova action).
```
    public function sendCaptainBookingManagementLink(Booking $booking)
    {
        $this->notify(new GuideBookingManagementLinkNotification($booking));
    }

    public function getCaptainBookingManagementLink(Booking $booking)
    {
        return URL::signedRoute('catpain.bookings.itenerary', [
            'email'   =>  $this->email,
            'booking'  => $booking
        ]);
    }
```
One thing I forgot to mention is that you can pass in parameters to the URL method singedRoute and it creates those as query parameters.  Now in all of my Notification classes I can access this singedURL by: 
` ->action('Accept or Decline', $notifiable->getCaptainBookingManagementLink($this->booking));`
action creates a button with the text / link and $notifiable can be whatever model you have the Notifiable trait on, in my case User (most cases).  
```
            return (new TwilioSmsMessage())
                ->content("Pending: Your FishAnywhere booking request will expire in ".(24 - $this->hoursAfterRequest)." hours.  Please accept or decline the trip: ".$notifiable->getCaptainBookingManagementLink($this->booking));```
Also the text that gets sent out woohoo!!!!!!!  

One last thing to do is to create the nova action so that we can send out a new link at any given time of the booking.  This can be while pending / confirmed / completed, the URL itself just returns a livewire component and depending on what stage the booking is on, certain information is displayed.  I reused 90% of the stuff from the captains dashboard, cause well why not.  It's an ugly page in my opinion and not needed but after released captains have had more success in using it than logging in.  My nova action looks like this:
```
    public function handle(ActionFields $fields, Collection $models)
    {
        $models->each(function ($booking) {
            $captain = $booking->trip->charter->user;
            $captain->sendCaptainBookingManagementLink($booking);
        });
    }
```
Hurray for eloquent relationships!!!!!!  So I get the captain from the booking and then send them the link.  

That's it for today folks.  Sorry again for doing a little venting at the beginning it's just 6am and the coffee is making me feel alive.  The biggest takeaway from this week at work is I probably need to start looking.  I mean let's be honest, my company thinks this mobile app is going to increase revenue / booking acceptance rates / yada yada yada.  But to make this all happen a captain must use the app, and how do they use the app?  Well they have to login, and if they cannot figure out how to do it on a web application, what makes anyone in their right mind think that this can be done on a phone / mobile application?  Unfortunately I should've pushed back at some point or set realistic expectations but I feel like that's not my job.  I'm paid to code / produce features / apps and that's what I do.  I'll have to leave the rest up to them.  That's it for this week.  Thanks for taking the time to read / listen.