guides
Guide

How to protect Laravel API with Magic Link

Magic Staff · January 15, 2021

A tutorial to demonstrate how to add authorization to a Laravel API with Magic's Laravel Plugin.

#Quickstart

$ git clone https://github.com/magiclabs/example-laravel-api.git
$ cd example-laravel-api
$ mv .env.example .env
$ composer install
$ php artisan serve

Update .env with your LIVE_SECRET_KEY

MAGIC_SECRET_API_KEY=sk_live_12A34V567B89

#Prerequisites

This guide covers building an API protected by Magic issued DID token. ⁠Mobile, Desktop, and Native applications typically consume this type of API using Magic’s Web, React Native, iOS, or Android Client SDK.

This guide demonstrates:

  • How to check for a Decentralized ID Token (DIDT) in the Authorization header of an incoming HTTP request.; if
  • How to check if the token is valid, using the validate() of Magic’s Laravel SDK

This guide assumes:

  • You have already configured your client-side app with the Magic Client SDK

  • Your Laravel Application is up and running. If not, please run the following command to get it started.

    #Via Composer

    composer create-project laravel/laravel magic-app
    
    
    cd magic-app
    
    
    php artisan serve

    #Via Laravel Installer

    Or, you may install the Laravel Installer as a global Composer dependency:

    composer global require laravel/installer
    
    
    laravel new magic-app
    
    
    cd magic-app
    
    
    php artisan serve

#Validate DID Tokens

#Install dependencies

Protecting your Laravel API requires a middleware to check for and verify a bearer tokeAuthorizationDecentralizedauthorizationorization header of an incoming HTTP request. We'll do that using tools provided by the Magic Laravel SDK.

composer require magiclabs/magic-laravel

#Configure the plugin

The magic-laravel plugin comes with a configuration file that can be generated using Artisan. First, generate the configuration file from the command line:

php artisan vendor:publish --provider="MagicLaravel\ServiceProvider"

After the file is generated, it will be located at config/magic.php.

// config/magic.php
return [
    /*
     |--------------------------------------------------------------------------
     | Secret API Key
     |--------------------------------------------------------------------------
     |
     | Your API secret key retrieved from https://dashboard.magic.link
     |
     */

    'secret_api_key' => env('MAGIC_SECRET_API_KEY', null),

    /*
     |--------------------------------------------------------------------------
     | HTTP request strategy
     |--------------------------------------------------------------------------
     |
     | Customize your HTTP request strategy when making calls to the Magic API
     |
     */

    'http' => [
        'retries' => env('MAGIC_RETRIES', 3), // Total number of retries to allow

        'timeout' => env('MAGIC_TIMEOUT', 10), // A period of time the request is going to wait for a response

        'backoff_factor' => env('MAGIC_BACKOFF_FACTOR', 0.02), // A backoff factor to apply between retry attempts
    ],
];

#Get your Magic Secret Key

Sign Up with Magic and get your MAGIC_SECRET_KEY.

Feel free to use the First Application automatically configured for you or create a new one from your Dashboard.

Edit the .env file to add the configuration values needed to verify the incoming DID tokens.

MAGIC_SECRET_API_KEY=sk_123456789
MAGIC_RETRIES=
MAGIC_TIMEOUT=
MAGIC_BACKOFF_FACTOR=

#Protect API Endpoints

The routes shown below are available for the following requests:

  • GET /api/public: available for non-authenticated requests
  • GET /api/private: available for authenticated requests containing a DID Token

For the private API route, we'll need middleware to check for a bearer token in an Authorization header for the request and verify that the token is valid. We'll create that middleware using the make:middleware Artisan command:

php artisan make:middleware CheckDIDT

Now, let's implement the handle() method that Laravel will call automatically for the route:

<?php
// App/Http/Middleware/CheckDIDT.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Magic;
use MagicAdmin\Exception\DIDTokenException;
use MagicAdmin\Exception\RequestException;

class CheckDIDT
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {

        $did_token = $request->bearerToken();

        if($did_token){

            try {
                //verifying the DID Token
                Magic::token()->validate($did_token);
                $user = Magic::user()->get_metadata_by_token($did_token);
                if (!$user) {
                    return response()->json(["message" => "Unauthorized user"], 401);
                }
            } catch (DIDTokenException $e) {
                return response()->json(["message" => $e->getMessage()], 401);
            } catch (RequestException $e) {
                return response()->json(["message" => "Request Exception"], 401);
            }

        } else {
            return response()->json(["message" => "Bearer token missing"], 401);
        }

        return $next($request);
    }
}

This middleware:

  • Retrieves the Bearer token from the request and call the validate function to verify the DIDT.
  • Uses the DID Token to retrieve User’s Meta Data Magic::user()->get_metadata_by_token($did_token)
  • Catches any exceptions thrown if the DIDT is expired, malformed or invalid. Also, checks for RequestException.

Next, we register this middleware in the HTTP Kernel with the name didt:

// App/Http/Kernel.php
// …
class Kernel extends HttpKernel {
	// ...
	protected $routeMiddleware = [
	    // ...
	   'didt' => \App\Http\Middleware\CheckDIDT::class,
	   // …
}

We are now able to protect individual API endpoints by applying the didt middleware:

// routes/api.php
use Magic;

// This endpoint does not need authentication.
Route::get('/public', function (Request $request) {
    return response()->json(["message" => "Hello from a public endpoint! You don't need to be authenticated to see this."]);
});

// These endpoints require a valid did token and fetches user's data using did token
Route::get('/private', function (Request $request) {
    return response()->json([
        "message" => "Hello from a private endpoint! You need to have a valid DID Token to see this.",
        "user" => Magic::user()->get_metadata_by_token($request->bearerToken())->data
        ]);
})->middleware('didt');

The /api/private route is now only accessible if a valid DID Token is included in the Authorization header of the incoming request.

#Obtaining DID Token

#Fork the template code on Codesandbox

To get the DID Token, fork our Laravel API Authorization template in CodeSandBox.

#Update the MAGIC_PUBLISHABLE_KEY

Replace the pk_live5A2E52658C87281B string with your Publishable API Key from the Magic Dashboard: on line 46

/* 2️⃣ Initialize Magic Instance */
const magic = new Magic('pk_live_5A2E52658C87281B');

#Live Frontend Application

You now have a working Frontend Application.

Login and copy the DID Token for Testing the API with Postman.

#Using your API

The /api/private route is now only accessible if a valid DID Token is included in the Authorization header of the incoming request.

Now, let’s start the Laravel server locally:

php artisan serve --port=8001

Send a GET request to the public route - http://localhost:8001/api/public - and you should receive back:

{
    "message": "Hello from a public endpoint! You don't need to be authenticated to see this."
}

Now send a GET request to the private route - http://localhost:8001/api/private - and you should get a 401 status and the following message:

{ "message": "Bearer token missing" }

Add an Authorization header set to Bearer DID_TOKEN using the token generated above. Send the GET request to the private route again, and you should see:

{
    "message": "Hello from a private endpoint! You need to have a valid DID Token to see this.",
    "user": {
        "email": "your_email@provide.com",
        "issuer": "did:ethr:0xaa12b334C1f3d……….62367e5B8e",
        "public_address": "0xaa12b334C1f3d……….62367e5B8e"
    }
}

#Done

Congratulations! You have successfully secured your Laravel API with Magic.

#What's Next

#Use Magic with existing tools

#Customize your Magic flow

You can customize the login experience using your own UI instead of Magic's default one and/or customize the magic link email with your brand. Learn how to customize.

Let's make some magic!