There is a question I get several times per year in the support of Passster. Is it possible to use password protection for a large number of passwords? How many passwords are too many?
After I set an imaginary cap to 10.000 codes people still asking for more and more codes to handle.
Last month I got a new client project with a crazy requirement.
We want to use Passster for our relaunch, but we need to make sure we can verify over a million codes under a second.
The client
Bummer. I initially thought this would never happen. Anyway, I like challenging tasks and projects so I jumped on board and started brainstorming.
Should that be handled inside of WordPress? No, simply no. This will be a project with A LOT of traffic and any additional server peak to hash, verify and save codes could have a negative impact on that.
Table of Contents
Microservices / API
Some of you coming from a developer background may know about the term “microservice architecture“. I’m a really big fan of the concept. A single service does one job pretty well and has an API to connect with others. Clean architecture, easier debugging, massive performance improvements.. you get it.
Microservices are a software development technique —a variant of the service-oriented architecture (SOA) structural style— that arranges an application as a collection of loosely coupled services.[1] In a microservices architecture, services are fine-grained and the protocols are lightweight.
Wikipedia
Lumen
Besides being a fan of WordPress and WooCommerce (obviously), I’m really excited about Laravel and it’s an entire ecosystem. Laravel makes PHP fancy, modern and scalable. It has awesome concepts like the console commands from Symfony, their caching solution and (maybe the best of all) their database query solution eloquent (especially compared to $wpdb..).
Passster
Protect your entire website, entire pages, or just parts of your content with one or more passwords.
But you sad microservice – is Laravel not a bit too big for that task? Yes. you’re right. That’s why I have chosen Lumen instead. Lumen is a microframework based on Laravel with just enough features to complete the job.
Lightning fast micro-services and APIs delivered with the elegance you expect.
Laravel Lumen
How to setup Lumen
I assume that you know how to use your terminal and how to use composer as a dependency manager. If not, start here.
Open your terminal, navigate to your project folder and create your Lumen app
php composer create-project — prefer-dist laravel/lumen passster-storage
This installs Lumen with all required dependencies in a folder called passster-storage.
.env
Now let’s modify the env-file. First, rename the file from .env.example to .env. Open the file and fill out the necessary credentials like database host, user and password, application URL and caching (if used).
Database and Migrations
As a first step, we create a migration to set up our database tables. Thankfully with artisan, it’s simple to achieve, use the following command in your terminal.
php artisan make:migration create_codes_table
This will create a new migration file for you. You can find it in database/migrations. Modify it to meet our requirements like so:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCodesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('codes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('code');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('codes');
}
}
After that, we can simply run the artisan command in our terminal to migrate it.
php artisan migrate
Code Model
We made our migration now it’s time to create our model. In your /sites/patrickposner.com/passster/files folder create a new file called Code.php.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Code extends Model {
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [ 'code' ];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = [];
}
bootstrap.php
Cause we are using Lumen instead of Laravel we need to activate some features like eloquent for our project. So open the file located in app/bootstrap.php and uncomment the following two lines:
//$app->withFacades();
//$app->withEloquent();
Controller
The next part of our puzzle is the controller. Navigate to app/http/controller and create a file Called CodeController.php with the following content:
<?php
namespace App\Http\Controllers;
use App\Code;
use Illuminate\Http\Request;
class CodeController extends Controller {
/**
* Create a new controller instance.
*
* @return void
*/
public function index() {
$codes = Code::all();
return response()->json( $codes );
}
public function create( Request $request ) {
$code = new Code;
$code->code = $request->code;
$code->save();
return response()->json( $code );
}
public function show( $code ) {
$code = Code::where( 'code', $code )->get();
return response()->json( $code );
}
public function update( Request $request, $code ) {
$code = Code::where( 'code', $code )->first();
$code->updated_at = new \DateTime();
$code->save();
return response()->json( $code );
}
public function destroy( $code ) {
$code = Code::where( 'code', $code )->get()->each->delete();
return response()->json( 'code removed successfully' );
}
}
Since there are mostly simple CRUD-methods I don’t want to explain it any further. One thing you will notice is the nice and speaking syntax coming from Laravel’s eloquent.
Routes
The last missing part of our lumen app are routes. Open the web.php file in /routes and paste the following content to it:
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/
$router->group( ['prefix'=>'api/v1'], function() use( $router ) {
$router->get( '/codes', 'CodeController@index' );
$router->post( '/code', 'CodeController@create' );
$router->get( '/code/{code}', 'CodeController@show' );
$router->put( '/code/{code}', 'CodeController@update' );
$router->delete( '/code/{code}', 'CodeController@destroy' );
});
That’s all for our Lumen app for now. Please keep in mind that this tutorial tends to be as minimal as possible. It’s highly recommended to use any kind of authentication and security layers to protect your routes against unauthorized access. Let’s keep it simple for now and go further with the WordPress plugin part.
Connector Plugin for Password Protection with Passster
I created a connector plugin, which connects to the Lumen API and use the hooks inside the Passster plugin to add the additional parameter to the verification process.
The plugin basically has two functions, one to communicate with the API and the second to unlock the content inside of Passster.
/**
* Check if code exists in code storage.
*
* @param string $code code to check.
* @return bool
*/
function passster_code_exists( $code ) {
global $wp_version;
// link to your lumen service API
$url = 'https://example.com/api/v1/' . $code;
$args = array(
'timeout' => 30,
'redirection' => 5,
'httpversion' => '1.0',
'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url(),
'blocking' => true,
'headers' => array(
'cache-control: no-cache',
),
);
$response = wp_remote_get( $url, $args );
if ( ! is_wp_error( $response ) ) {
$body = json_decode( $response['body'] );
if ( ! empty( $body ) ) {
return true;
}
} else {
$error_string = $result->get_error_message();
echo '<div id="message" class="error"><p>' . esc_html( $error_string ) . '</p></div>';
}
return false;
}
This method checks if the given code exists in your data storage. You need to add your Lumen service URL, it connects via the WordPress HTTP API and if the code exists it returns true else it returns false.
The last step is to hook into the correct action inside of Passster:
add_filter( 'passster_api_validation', 'passster_check_code' );
/**
* Check code via REST API
*
* @param bool $status is code in storage.
* @return bool
*/
function passster_check_code( $code ) {
$status = false;
$code_valid = passster_code_exists( $code );
if ( true === $code_valid ) {
$status = true;
}
return $status;
}
That’s basically all that it takes to build a microservice that can connect with Passster and verify a large number of codes in a second or less. It was an awesome journey and a seriously challenging task. Maybe it’s useful for one or another.
Passster
Protect your entire website, entire pages, or just parts of your content with one or more passwords.