How to protect URLs with a PIN code in Laravel
Tags: laravel, route
Published: 2020-05-26 16:10:15

---
title: "How to protect URLs with a PIN code in Laravel"
published: true
description: How to protect URLs with a PIN code in Laravel
tags: laravel, route
canonical_url: https://martinbetz.eu/articles/laravel-pin-protected-url/
---
Here is the user story for this post: As a website owner I only want to allow access to my website for visitors who enter the correct PIN.

## How the minimum version will work
When you enter the page for the first time, you will just see an input to enter a PIN. When you enter the wrong PIN, it fails silently and just reloads the page. If you enter the right PIN, it will save a cookie `access` and shows you the welcome page. If you try more than 3 times within a minute, you get a message `Too Many Requests`.

And here is how it will look in the browser:

<img class="border-2 rounded border-grey shadow-inline" src="/assets/img/articles/pin-access-demo.gif" alt="">

## The tests

I will first create the sad path where I cannot access the welcome page as I did not enter the right PIN. Then I will simulate that I previously entered the right PIN and have a cookie that lets me pass. Then I will test that actual entering of the PIN and check whether I get a cookie. Lastly I check that I get banned if I enter three wrong PINs in a very short time:

```php
<?php

namespace Tests\Feature\Http\Controllers;

use Illuminate\Support\Facades\Config;
use Tests\TestCase;

class WelcomeTest extends TestCase
{

    /** @test */
    public function can_not_access_welcome_page_without_pin() {
        $response = $this->get(route('root'));
        $response->assertStatus(302);
        $response->assertRedirect(route('pin.create'));
    }

    /** @test */
    public function can_access_welcome_page_with_pin_cookie() {
        $response = $this->withCookie('access', 'pass')->get(route('root'));
        $response->assertStatus(200);
    }

    /** @test */
    public function can_enter_pin_and_access_root_page() {
        Config::set('settings.PIN', '5678');
        $response = $this->post(route('pin.store', [
            'pin' => '5678',
        ]));
        $response->assertCookie('access', 'pass');
    }

    /** @test */
    public function blocks_for_one_minute_after_three_attemps() {
        $this->post(route('pin.store', [
            'pin' => '1',
        ]));
        $this->post(route('pin.store', [
            'pin' => '2',
        ]));
        $this->post(route('pin.store', [
            'pin' => '3',
        ]));
        $response = $this->post(route('pin.store', [
            'pin' => '3',
        ]));
        $response->assertStatus(429);
    }
}
```

## The code

I wrote the tests first and added the code one error after another. You may replicate by just running the tests above and go through the errors yourself.

Here's the files you need with a short explanation what they do:

- `routes/web.php`: for adding the routes, adding the middleware to restrict access and limit the trials
- `app/Http/Middleware/CheckPin.php`: this is the middleware that checks whether you have a cookie that allows you to enter the welcome page
- `app/Http/Kernel.php`: this is were you register the custom middleware
- `app/Http/Controllers/PinController.php`: this is were you check the PIN entered and create the cookie on success
- `resources/views/create.blade.php`: the view for entering the PIN
- `resources/views/welcome.blade.php`: the view with the protected content
- `config/settings.php`: you define the PIN setting here to get the value from the `.env` file and provide a fallback value
- `

And here are the important sections of the files. I marked omissions with `(…)`. You can find the full source code on [https://github.com/minthemiddle/pin-tutorial](https://github.com/minthemiddle/pin-tutorial):

```php
// routes/web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
})->name('root')->middleware('pin');

Route::get('pin/create', function () {
    return view('create');
})->name('pin.create');

Route::post('pin/store', 'PinController@store')->name('pin.store')->middleware('throttle:3,1');

```

```php
// app/Http/Middleware/CheckPin.php
<?php

namespace App\Http\Middleware;

use Closure;

class CheckPin
{
    public function handle($request, Closure $next)
    {
        if ($request->cookie('access') === 'pass') {
            return $next($request);
        }

        return redirect(route('pin.create'));
    }
}

```

```php
// app/Http/Kernel.php
<?php

namespace App\Http;

use App\Http\Middleware\CheckPin;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    (…)
    
    protected $routeMiddleware = [
        (…)
        \App\Http\Middleware\CheckPin::class,
    ];
}

```

```php
// app/Http/Controllers/PinController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;

class PinController extends Controller
{
    public function store(Request $request)
    {
        if ($request->pin === Config::get('settings.PIN')) {
            return redirect(route('root'))->withCookie('access', 'pass', 60);
        }

        return redirect(route('pin.create'));
    }
}

```

```html
// resources/views/create.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://unpkg.com/@tailwindcss/custom-forms@0.2.1/dist/custom-forms.min.css">
    <title>Enter PIN</title>
</head>
<body class="p-8 mx-auto">
<form action="{{ route('pin.store') }}" method="POST">
    @csrf
    <input type="number" required name="pin" class="form-input text-xl">
    <button type="submit" class="form-input p-2 text-xl">Save</button>
</form>
</body>
</html>

```

```php
// resources/views/welcome.blade.php
// I just used the standard welcome page that ships with Laravel

```php
// config/settings.php
<?php

return [
    'PIN' => env('PIN', '1234'),
];

```

```yaml
# .env
(…)
PIN=5678
(…)
```