Awwwards
Canvas and Laravel Passport's Hero Image

Canvas and Laravel Passport

It's getting a little exciting over here as we near the launch of our first software product: Helix. We're ramping up our marketing efforts and as a small shop that means we're delaying product development so that we can roll out a clean marketing site. We hold ourselves to high standards in the things that we build (hopefully that comes through in Metric Loop's website and blog) which can lead us, at times, to zoom in so close to a feature that we lose sight of the big picture. Knowing this about myself and my team, I wanted to make sure we weren't shooting ourselves in the foot by spending too much time on a marketing site.

Requirements

We first had to identify what exactly this marketing site was going entail. We need a great landing page with a clear call-to-action, a blog to build rapport and trust with our audience and target market, a page that highlights features of the product, a pricing page as we get closer to launch day, and a press page for press releases and our branding/marketing assets.

In a nutshell, we want a simple blogging platform that we can expand to have static pages like features, pricing and press. That's where Canvas comes in. We spent about 2 months developing and launching the current metricloop.com site and blog. We could have copied that codebase over but that would mean having two diverging code bases that we have to maintain. So, in the name of launching quickly we decided to start with Canvas.

Extending Canvas

There are a couple limitations though. It's an active open source project so there are going to be a few hiccups in the code. We've already submitted a PR on Github because we know how valuable a project liks Canvas is.

But there are other things that don't make sense as pull requests. For one, Canvas is a superbly minimal blogging platform. It doesn't require that each blog post have a featured page_image which is something that we want to enforce. And if we require the page_image we would also want to generate a thumbnail image with certain dimensions so that there's consistency on the blog index page. Canvas ties in directly with a media library manager so it doesn't allow for easy manipulation or thumbnail generation. If we wanted that feature, we would have to implement it ourselves but how important is an image thumbnail when you're just trying to launch? For now, we've decided that we will police ourselves so that featured page_images have the same dimensions.

Canvas is also not meant to handle multiple authors so a PR to add that functionality would probably be rejected on principle. And that's totally cool. In fact, we love it when maintainers own the direction of the project.

But we need multiple authors. We tossed around the idea of adding multiple author support to Canvas but this little voice in the back of my mind kept reminding me that we just need to launch something. What's a quicker way that we can attribute posts to different authors? Maybe the simplest way would be to add a field to each Post and manually type in the data every time. But we need something a little more robust than that. We already have user accounts for our employees on metricloop.com that have things like name, email, picture, biography, and social media accounts. We wanted to avoid having yet another user account to manage so we figured we could just pull the User account information from Metric Loop and dump it into the Helix HQ website. Then, when creating or updating a Post, you can select an author from a drop-down that is populated from the Metric Loop user database.

But how do we pull that information over securely?

Laravel Passport

Passport solves this problem for us with Personal Access Tokens. Basically, by enabling Passport on metricloop.com we can expose a simple API that allows us to grab Users data quickly, easily, and securely. The walkthrough is incredibly easy especially if you're already using Vue and you follow the front-end quickstart.

I followed everything to a T and even downloaded Postman so that I could test the endpoints. I knew that I was going to need an endpoint to fetch all Users and an endpoint to fetch a single User by id.

// routes/api.php


Route::get('/users', function (Request $request) {
    return App\User::where('id', '>', 1)->get()->sortBy('order')->toJson();
});


Route::get('/users/{id}', function (Request $request, $id) {
    return App\User::where('id', $id)->first()->toJson();
});

Done. So I fired up Postman, added the Authorization header with Bearer <token>, the Accept header with application/json and hit send and.... got a 404. If anything I thought I would have gotten a 401 Unauthorized, 403 Forbidden or a 500 Server Error. But a 404? That didn't really make sense. I went back over the documentation, fiddled with middleware, tried to tweak the Http/Kernel.php file, and even tried to hit the route from the browser and kept getting a 404. I spent way too long only getting a 404 when trying to hit /api/*.

Then I looked in the app/Providers/RouteServiceProvider.php and saw something that caught my eye.

// RouteServiceProvider.php


/**
 * Define the routes for the application.
 *
 * @return void
 */
public function map()
{
    $this->mapWebRoutes();
    $this->mapApiRoutes();
}

Here, the Provider maps the routes/web.php before the routes/api.php. And then a freaking lightbulb went off and I mumbled to myself "you've got to be kidding me...".

Here's what the end of routes/web.php looks like:

// routes/web.php


Route::get('{all}', function () {
    abort(404);
})->where('all', '.*');

That means that every single route in the api.php file was getting caught and triggering a 404. So I ripped that chunk out of web.php and added the following to RouteServiceProvider.php.

// RouteServiceProvider.php


/**
 * Define the routes for the application.
 *
 * @return void
 */
public function map()
{
    $this->mapWebRoutes();
    $this->mapApiRoutes();
    $this->map404Catcher();
}


/**
 * Define a 404 catcher.
 */
protected function map404Catcher()
{
    Route::get('{all}', function () {
        abort(404);
    })->where('all', '.*');
}


Then I created a new Personal Access Token, fired up Postman, hit the endpoint, and got the appropriate response! What a relief.

Take Note

One thing you want to be careful with is the php artisan passport:install function. It actually generates a private and public key that your new OAuth 2.0 server uses to authorize Clients and Personal Access Tokens. When it creates the two keys, it inserts a record for each into the oauth_ tables. The Artisan command places the keys in the /storage directory. Don't commit these to source control. Create a .gitignore file in the /storage directory with the following:

// storage/.gitignore


oauth-private.key
oauth-public.key

When you deploy to production, after you migrate the new oauth_ tables, you need to run php artisan passport:install in production. Otherwise your app won't be able to issue tokens.

Clone Users From Metric Loop to HelixHQ

Because we're only using Personal Access Tokens and not authorizing our new site as a true OAuth client, we're able to take a little bit of a lazy route. Remember, we're just trying to launch something quickly without getting bogged down with implementation details. We've yet to implement this part, but I can tell you how it's going to work.

We're going to create a new model in our Canvas project (the Helix marketing site, HelixHQ) called Author. It's basically going to be an exact clone of the User model from metricloop.com without the authentication details. We're also going to add an author_id to the Post model so that we can attribute the post to the correct author.

Then sometime in the middle of the night, we're going to issue a Command that will hit the metricloop.com/api/users endpoint and clone the results into the HelixHQ::Author table. We're also going to allow a way to trigger the command manually so that we can grab fresh MetricLoop::User data whenever we might need it.

Closing Thoughts

All in all, we’ve done a pretty good job focusing on the minimum amount of effort to get the job done. As developers, we know that with enough time we can make anything a reality. But that’s why this project was such an awesome exercise in discipline and forward-thinking. We could have developed a bunch of extra features and extended our launch (and time away from Helix) by a few weeks. Instead, we spent a few days cranking out a marketing site that does exactly what we need. And we’re looking forward to sharing it with y’all soon!


Patrick Guevara's Profile Picture

Patrick Guevara


Chief Software Engineer

Patrick cofounded Metric Loop on the dream of building really great software with a clean, transparent approach. He lives in Austin, Texas with his wife Jess and their dog named Moose.