Laravel

Laravel Valet for Production Domains

January 8, 2023 · 2 min read

Recently, after a brief outage at work, I wondered if it would be possible to replicate the problem locally using Laravel Valet. My Google search landed on this StackOverflow post, where the answers shot down the idea. Not to be dissuaded by something I read on the internet, I started investigating if it was possible and stumbled upon what I think is a viable solution. There aren't very many hoops to jump through or major quirks so I believe it's not only possible but could be supported out of the box.

In my case, I want to proxy the domain scdn-app.thinkorange.com through my local version of the Laravel application.

  1. Edit ~/.config/valet/config.json on macOS and change the tld parameter from test to com.
  2. Change to the directory of your application.
  3. Run the command valet link scdn-app.thinkorange to set up our valet configuration to point the domain to this directory.
  4. Run the command valet secure scdn-app.thinkorange to set up the SSL certificate.
  5. Change the directory to dnsmasq cd ~/.config/valet/dnsmasq.d.
  6. Copy the existing TLD config to cover the .com domain with the command cp tld-test.conf tld-com.conf.
  7. Edit the new file to change the first address line to address=/.com/127.0.0.1 and save the file.
  8. (Optionally) Isolate the site to PHP 8.1 with the command valet isolate --site scdn-app.thinkorange php@8.1.
  9. Change your /etc/hosts file to redirect the domain to 127.0.0.1 for ipv4 and ::1. I use the excellent Gas Mask to make this step easier.

Now we should have a functional production proxy through our local machine. This configuration creates a few problems around keeping the com TLD. Fortunately, a few extra steps are necessary for us to switch back to .test while also keeping this site functional.

  1. Edit ~/.config/valet/config.json again and change the tld parameter from com back to test. This change will immediately break our site.
  2. Change to the Sites directory cd ~/.config/valet/Sites.
  3. If we use ls -al to list the directory, we'll see our site scdn-app.thinkorange. Let's change that.
  4. Run the command mv scdn-app.thinkorange scdn-app.thinkorange.com.

Our site should now be working again. We are also able to continue serving our previous local test domains.

Because we can create a permanently functional system using these steps, I believe it should be possible to create a pull request to reduce the number of hoops we have to jump through. I'd love to be able to run valet link scdn-app.thinkorange.com. with a period at the end to denote I'm including the full domain with TLD. That would eliminate the temporary step of editing the config.json file, and the Sites directory would just work(TM) as it would include the .com directory name. I don't believe we even need the dnsmasq changes as I'm able to navigate to a functional site without them. I believe Gas Mask is doing the work, but it's better to be safe than sorry.

If you'd prefer a YouTube video where I stumble through recreating these steps from scratch:

Laravel Passport usage with Swaggervel v2.3

July 10, 2018 · 5 min read

Overview

I've been using this Swaggervel package with almost all my recent Laravel projects. A few instances were lightly customized to work against different authentication schemes and I only briefly touched on using Laravel Passport.

I wanted to highlight a few areas while also offering up an example project as a lightly opinionated jumping off point. Just the highlights cover quite a bit of information but the example should have ample information in commit messages and in the finished product.

Setting up Laravel and Laravel Passport

First we run laravel new <project_name>, git init and commit immediately to mark our base Laravel installation. I've always preferred this immediate commit over making customizations first as it's far easier to track your customizations versus the base install. Next, we run through the Laravel Passport docs with the following caveats:

  • php artisan vendor:publish --tag=passport-migrations doesn't copy the migrations as expected. We manually do this.
  • php artisan migrate --step creates a migration batch for each migration file individually. This lets us rollback to individual steps and is primarily personal preference.
  • app/Providers/AuthServiceProvider contains the following:
Passport::routes(function (RouteRegistrar $routeRegistrar) {
    $routeRegistrar->all();
});
Passport::tokensCan([
]);
Passport::enableImplicitGrant();
Passport::tokensExpireIn(Carbon::now()->addDays(15));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
  • Run artisan make:auth to utilize the app layout and create a home view that is protected by the Login prompt.

    • The Passport Vue components could be displayed on the welcome page but we're attempting to set future users up for better practices.
  • Create a proper WelcomeController with matching view that utilizes the same app layout

    • This is not necessary but this one step makes it possible to properly utilize artisan route:cache in the future as route closures aren't supported.

Setting up Swaggervel

Now that the basics are complete, we bring in Swaggervel via composer require appointer/swaggervel --dev. We can ignore the line in the documentation that mentions adding Appointer\Swaggervel\SwaggervelServiceProvider::class as that's only for Laravel versions earlier than 5.5 without package discovery. It's necessary to run artisan vendor:publish to publish the content as we're using this package as a dev dependency and the assets won't show up otherwise. Now that Swaggervel is in place we can bring it all together.

To start, we create the file app/Http/Controllers/Api/v1/Controller.php as our generic API base controller. This controller houses our root-level @SWG\Info definition in a convenient location. This also sets us up for future work where API controllers are versioned, though this is personal preference. The secret sauce is the @SWG\SecurityScheme annotation:

/**
 *   @SWG\SecurityScheme(
 *     securityDefinition="passport-swaggervel_auth",
 *     description="OAuth2 grant provided by Laravel Passport",
 *     type="oauth2",
 *     authorizationUrl="/oauth/authorize",
 *     tokenUrl="/oauth/token",
 *     flow="accessCode",
 *     scopes={
 *       *
 *     }
 *   ),
 */

The securityDefinition property is arbitrary but needs to be included in every protected route definition. You can specify multiple security schemes to cover things like an generic api key or likely multiple OAuth flows, though I haven't tried working out the latter. These are the supported flows and it's important to note that Swaggervel is currently on the OpenAPI 2.0 specification, though this may change in the future. The scopes specified includes everything (*) but we could define any scopes explicitly. It should be noted that we also need to setup the route definitions in our resource Controller classes but due to the verbosity they are too much to include in this post. A small snippet that is unique to working with this setup is the following:

*   security={
*     {
*        "passport-swaggervel_auth": {"*"}
*     }
*   },

This tells a specific endpoint to use the securityDefinition created earlier and it's important that these match. The example project has rudimentary UserController, User model, and UserRequest definitions that should be a decent starting point, though I can't vouch for them being very comprehensive.

Configuring our OAuth Client

First we need to create an OAuth client specifically for Swaggervel connections. Go to the /home endpoint and under OAuth Clients click Create New Client. Under Name specify Laravel Passport Swaggervel or just Swaggervel. Under Redirect URL we're unable to specify /vendor/swaggervel/oauth2-redirect.html directly, so use a placeholder like https://passport-swaggervel.test/vendor/swaggervel/oauth2-redirect.html instead. Using your SQL toolbox of choice, navigate to the table oauth_clients and look for the row with the name specified in the previous step, in our case Laravel Passport Swaggervel. Manually update the redirect column to /vendor/swaggervel/oauth2-redirect.html.

Now that our OAuth client in Passport should be setup correctly, we focus our attention on the config/swaggervel.php settings. The client-id should be set to what Passport shows in the UI as the Client ID field. This is also the id of the row in the oauth_clients table. The client-secret should be set to the what Passport shows in the UI as the Secret field. We also set both secure-protocol and init-o-auth to true, the latter of which fills in the UI with our secrets otherwise we'd have to put them in manually.

Correcting Swagger UI to Capture Tokens

For the OAuth2 redirect to function properly we need to modify the Swagger UI configuration in resources/views/vendor/swaggervel/index.blade.php. Under const ui = SwaggerUIBundle({ right below the url parameter should be oauth2RedirectUrl: '/vendor/swaggervel/oauth2-redirect.html',. This reinforcement is necessary as the Swagger UI doesn't 'catch' the tokens properly without this. Other notable additions that make the UI slightly easier to work with:

tagsSorter: 'alpha',
operationsSorter: 'alpha',
docExpansion: 'list',
filter: true

Testing Authentication via the Swagger UI

First we go to the api/docs endpoint to display the Swagger UI. Click the Authorize button with the unlocked padlock icon. Verify the client_id and client_secret sections are filled in. Click Authorize and the Laravel Passport screen labelled Authorization Request should display with the Authorize and Cancel buttons. Click Authorize again and you should be redirected back to Swagger with the client_id and client_secret now showing as ****** with a Logout button instead of Authorize. We should now be able to click on the GET /users route, click the Try it out button, click on the blue Execute button and be greeted with our expected response as a list of users.

Conclusion

We've hopefully highlighted the basic touch points of the process with the example code going into much further detail. The project is lightly opinionated to facilitate practices that have served me well so far. It is by no means a complete reference but it should be a good jumping off point when it's somewhat harder to see the big picture without a comprehensive example.

In case you need the link to the project again.

Laravel Passport displays Basic Authentication prompt

September 27, 2017 · 1 min read

Laravel Passport prompt

I've been bitten by this issue so many times that I have a form of amnesia where I forget that it happened all over again. This github issue highlights the problem but I'm more of a visual learner.

The problem can be traced back to configuring the redirect_uri parameter incorrectly. OAuth2 highly requires that the callbacks are identical between the server and consumer(s). For consumers that are external to the app, this is almost never a problem. For first-party consumers like Swagger(vel), this is extremely easy to configure incorrectly.

Revisiting Laravel Homestead MySQL Password Expiration

January 8, 2017 · 1 min read

After putting the solution in my previous post through its paces for a few weeks, I realized the less intrusive approach is to patch Homestead v2's scripts/create-mysql.sh with the following snippet:

#!/usr/bin/env bash

cat > /etc/mysql/conf.d/password_expiration.cnf << EOF
[mysqld]
default_password_lifetime = 0
EOF

service mysql restart

DB=$1;

mysql -e "CREATE DATABASE IF NOT EXISTS \`$DB\` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci";

This change pipes the default_password_lifetime setting into the file /etc/mysql/conf.d/password_expiration.cnf and restarts the mysql service. The provisioning process then can proceed as normal.

This approach requires no updated vagrant virtualbox image or other similar adjustments and allows us to keep using version 0.3.3 indefinitely.

I'm likely going to abandon my settler and homestead forks as I couldn't adequately maintain them moving forward. I'll work to push this upstream as I feel it should be implemented there.

Addressing Laravel Homestead MySQL Password Expiration

January 7, 2017 · 3 min read

On November 7th 2016, I was hit with a peculiar issue I've never seen before working in a provisioned Homestead box. The exception:

PDOException in Connector.php line 55:
SQLSTATE[HY000] [1862] Your password has expired. To log in you must change it using a client that supports expired passwords.

Firing up a different vagrant machine, I was greeted with the same problem. This seemed to affect all of the vagrant boxes using version laravel/homestead (virtualbox, 0.3.3).

On the machine, the MySQL version displayed by mysql --version is

mysql  Ver 14.14 Distrib 5.7.9, for Linux (x86_64) using  EditLine wrapper

MySQL 5.7's password expiration policy seemed to point to the culprit.

From MySQL 5.7.4 to 5.7.10, the default default_password_lifetime value is 360 (passwords must be changed approximately once per year). For those versions, be aware that, if you make no changes to the default_password_lifetime variable or to individual user accounts, all user passwords will expire after 360 days, and all user accounts will start running in restricted mode when this happens.

Looking at the list of users with relevant columns shown that the password for the user homestead was set on 2015-11-13 03:50:18.

mysql> select host, user, authentication_string, password_expired, password_last_changed, password_lifetime from mysql.user;
+-----------+-----------+-------------------------------------------+------------------+-----------------------+-------------------+
| host      | user      | authentication_string                     | password_expired | password_last_changed | password_lifetime |
+-----------+-----------+-------------------------------------------+------------------+-----------------------+-------------------+
| localhost | root      | *14E65567ABDB5135D0CFD9A70B3032C179A49EE7 | N                | 2016-11-08 22:28:11   |              NULL |
| localhost | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | N                | 2015-11-13 03:50:10   |              NULL |
| 0.0.0.0   | root      | *14E65567ABDB5135D0CFD9A70B3032C179A49EE7 | N                | 2015-11-13 03:50:15   |              NULL |
| 0.0.0.0   | homestead | *14E65567ABDB5135D0CFD9A70B3032C179A49EE7 | N                | 2015-11-13 03:50:18   |              NULL |
| %         | homestead | *14E65567ABDB5135D0CFD9A70B3032C179A49EE7 | N                | 2015-11-13 03:50:18   |              NULL |
+-----------+-----------+-------------------------------------------+------------------+-----------------------+-------------------+
5 rows in set (0.00 sec)

Date manipulation in PHP showed that 360 days from 2015-11-13 03:50:18 is 2016-11-07 03:50:18, about the time this started occurring.

It was later that I discovered this pull request didn't make it into branch revert-56-master used to build the 0.3.3 box. It succinctly described the problem at hand.

I saw 4 possible choices for a permanent solution:

  1. Set default_password_lifetime=0 explicitly in /etc/mysql/my.cnf.
  2. Upgrade MySQL to 5.7.11 or higher as the default was changed from 360 to 0.
  3. Create the homestead user with the keywords PASSWORD EXPIRE NEVER to disable password expiration for that user.
  4. All of the above.

Solution

In looking to correct upstream, the pull request was denied with very good reason. It was a ton of work to seemingly get the 5.6 branch up to master and I have absolutely no guarantee that something wasn't broken in the process.

Not being content with abandoning that work, I pushed a vagrant virtualbox image that should continue the 5.6 branch forward for the foreseeable future. There is one major caveat, it requires a patch to Homestead v2 to accommodate the changes introduced.

Steps required to use the image:

  1. Add the new vagrant image: vagrant box add w0rd-driven/homestead.
  2. Add the line box: "w0rd-driven/homestead" in Homestead.yaml to specify a different vagrant box than the default of laravel/homestead.
  3. Add the line "laravel/homestead": "2.0.x-dev" to the require-dev section of composer.json.
  4. Add or update the repositories section of composer.json:
"repositories": [
    {
        "type": "git",
        "url": "https://github.com/w0rd-driven/homestead.git"
    }
],
  1. Run composer update to change to the new composer package.
  2. Rebuild your box using vagrant destroy -f then vagrant up.
  3. Test everything.

I've enabled issues on both forks of settler and homestead. Unfortunately, I don't have VMWare Fusion to build the vmware provider image. If anyone has the capabilities, I would gladly grant the access to push the image.