How To Validate GitHub Webhooks With Laravel and PHP
Tags: php, laravel, github, security
Published: 2020-01-28 19:33:06

# How To Validate GitHub Webhooks With Laravel and PHP

### Introduction

[GitHub webhooks](https://developer.github.com/webhooks) are sent to your endpoint when events occur in a repository. This is a powerful extensiblity feature that provides the developer a platform for many use cases such as push notifications and automatically deploying code when a repository has new commits. As security aware developers, we should always validate our incoming webhooks against a known token.

In this post, I'll show how to properly validate webhooks from GitHub. When we're done, you can use this code to validate your own hooks.

At the end you'll have an API endpoint in the form of
https://example.com/api/webhooks/github/handle that's validated by a method with the following signature:

```php
protected function validateGithubWebhook($known_token, Request $request);
```
## Prerequisites

Before you begin this guide you'll need the following:

- A repository on GitHub
- A public server that can accept HTTP requests from GitHUb
- Laravel application

If you don't have a public server available, then you can sign up for a trial at [Amezmo](https://www.amezmo.com/register) and deploy your application in seconds without having to setup Nginx, or PHP.

## Step 1 — Generate a Secret Token

First, let's open up a shell and generate a random token that we'll use for our GitHub webhook:

```bash
openssl rand -hex 32
```
With the above command executed, copy the value into your `.env` file replacing the <output from command> with the result of your command.

```bash
GITHUB_WEBHOOK_SECRET=test
```

After you've wired up the .env file, be sure to define the configuration entry in `config/app.php`. Add the following entry to the array.


```php
'github_webhook_secret' => env('GITHUB_WEBHOOK_SECRET'),
```

## Step 2 - Define the API Route in Laravel

Finally, let's define our API endpoint in our Laravel application. This endpoint will be an API endpoint type that will not set a session cookie. Since this endpoint will not authenticated against a user session, we do not need to set a cookie in the HTTP response.

Open up `routes/api.php` and define a new [route group](https://laravel.com/docs/5.8/routing#route-groups)

```php
Route::namespace('Webhooks')->prefix('/webhooks')->group(function () {
    Route::post('/github/handle', 'GithubWebhookProcessor@handle');
});

```
We've defined a route group with the name Webhooks. This allows us to stay generic while providing a path
to extensibility in case we want to add other webhook providers in the future.

## Step 3. Create a webhook on GitHub.

Using any repository that you have on GitHub, create a webhook from the repository settings page.

Our webhook will be a JSON payload with just the push event for now. Use the token we generated in the first step for the Secret. Replace example.com with your own domain.

![GitHub Webhook Form](https://dev-to-uploads.s3.amazonaws.com/i/lpbvd8r4597yp3ylgt35.png)

Next, we'll define our folder and PHP file where we'll define our class to handle this route.

## Step 4 — Compose the Webhook Class

Create the Webhooks folder under `app/Https/Controllers`. Under our new `Webhooks` folder, define our new class in `GithubWebhookProcessor.php`.

```php
<?php

namespace App\Http\Controllers\Webhooks;

use Illuminate\Http\Request;
use Psr\Log\LoggerInterface;

class GithubWebhookProcessor
{
    /** @var \Psr\Log\LoggerInterface */
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * Validate an incoming github webhook
     *
     * @param string $known_token Our known token that we've defined
     * @param \Illuminate\Http\Request $request
     *
     * @throws \BadRequestHttpException, \UnauthorizedException
     * @return void
     */
    protected function validateGithubWebhook($known_token, Request $request)
    {
        if (($signature = $request->headers->get('X-Hub-Signature')) == null) {
            throw new BadRequestHttpException('Header not set');
        }

        $signature_parts = explode('=', $signature);

        if (count($signature_parts) != 2) {
            throw new BadRequestHttpException('signature has invalid format');
        }

        $known_signature = hash_hmac('sha1', $request->getContent(), $known_token);

        if (! hash_equals($known_signature, $signature_parts[1])) {
            throw new UnauthorizedException('Could not verify request signature ' . $signature_parts[1]);
        }
    }


    /**
     * Entry point to our webhook handler
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return mixed
     */
    public function handle(Request $request)
    {
        $this->validateGithubWebhook(config('app.github_webhook_secret'), $request);

        $this->logger->info('Hello World. The GitHub webhook is validated');
        $this->logger->info($request->getContent());
    }
}

```

## Step 4 — Test it

We've wired everything up on our end, now we can test our webhook by creating a new commit in the repository where we defined our webhook. 

## Conclusion
[Link to this repository on GitHub](https://github.com/amezmo/github-webhook-validation)