Skip to content

Instantly share code, notes, and snippets.

@martindrapeau
Created November 23, 2020 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martindrapeau/9ce72154a1ebe780ef79d37d306c1b80 to your computer and use it in GitHub Desktop.
Save martindrapeau/9ce72154a1ebe780ef79d37d306c1b80 to your computer and use it in GitHub Desktop.
Laravel Middleware to verify POST and GET requests from Canva
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
/**
* Class VerifyCanvaRequest.
*/
class VerifyCanvaRequest
{
protected $key;
public function __construct()
{
$secret = env('CANVA_SECRET');
$this->key = base64_decode(strtr($secret, '-_', '+/'));
}
public function handle(Request $request, Closure $next)
{
if ($request->isMethod('post')) $this->verifyPostRequest($request);
if ($request->isMethod('get')) {
if (app()->environment('local') && $request->exists('bypass')) {
// Debug in dev simply pass bypass in query string
return $next($request);
}
$this->verifyGetRequest($request);
}
return $next($request);
}
public function getCanvaPath(Request $request)
{
// Change these to match your endpoints
$paths = [
'api/canva/configuration' => '/configuration',
'api/canva/publish/resources/find' => '/publish/resources/find',
'api/canva/publish/resources/get' => '/publish/resources/get',
'api/canva/publish/resources/upload' => '/publish/resources/upload',
];
$path = $request->path();
foreach ($paths as $pattern => $canva) {
if (strpos($path, $pattern) !== false) return $canva;
}
abort(404);
}
public function verifyPostRequest(Request $request)
{
$version = 'v1';
$timestamp = $request->header('X-Canva-Timestamp');
$path = $this->getCanvaPath($request);
$body = $request->getContent();
$message = "{$version}:{$timestamp}:{$path}:{$body}";
$signatures = $request->header('X-Canva-Signatures');
$signature = hash_hmac('sha256', $message, $this->key, false);
if (strpos($signatures, $signature) === false) abort(401);
if (abs(now()->timestamp - $timestamp) > 300) abort(401);
}
public function verifyGetRequest(Request $request)
{
$version = 'v1';
$timestamp = $request['time'];
$user = $request['user'];
$brand = $request['brand'];
$extensions = $request['extensions'];
$state = $request['state'];
$message = "{$version}:{$timestamp}:{$user}:{$brand}:{$extensions}:{$state}";
$signatures = $request['signatures'];
$signature = hash_hmac('sha256', $message, $this->key, false);
if (strpos($signatures, $signature) === false) abort(401);
if (abs(now()->timestamp - $timestamp) > 300) abort(401);
}
}
@martindrapeau
Copy link
Author

Laravel Middleware to verify that an HTTP request originated from Canva. Necessary if you build an App on the Canva Apps Marketplace.

If a verification fails for a POST or GET request, the middleware will abort the request and return a 401 not authorized response. Translated from the original JavaScript code on their website. Read the docs to understand the logic.

To use

  • In your .env, set CANVA_SECRET with your secret
  • Modify the code to register your own routes
  • In Kernel.php, register the middleware. For example:
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'canva' => \App\Http\Middleware\VerifyCanvaRequest::class,
];
  • Make sure to set its priority before authentication. For example:
protected $middlewarePriority = [
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\VerifyCanvaRequest::class,
    \App\Http\Middleware\Authenticate::class,
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];
  • In your API routes for Canva, make sure to use the middleware. For example:
Route::group(['middleware' => ['canva']], function() {
	Route::post('/canva/configuration', [CanvaPublishController::class, 'configuration']);
	Route::post('/canva/publish/resources/find', [CanvaPublishController::class, 'resources_find']);
	Route::post('/canva/publish/resources/get', [CanvaPublishController::class, 'resources_get']);
	Route::post('/canva/publish/resources/upload', [CanvaPublishController::class, 'resources_upload']);
});
  • In your Redirect-URL route (for connection), make sure to use it as well. For example:
Route::group(['middleware' => ['canva']], function() {
    Route::get(
        'canva/authenticate',
        [CanvaController::class, 'authenticate']
    );
});

Testing the login page in your local/dev environment

If you require an authentication step and want to test it locally, you can set &bypass in the query string. The middleware will bypass verification. Look at the code for details.

@alextreppass
Copy link

Thanks for this Martin!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment