DEV Community

Cover image for How to protect Laravel API with Magic Link
Mohammad Shahbaz Alam
Mohammad Shahbaz Alam

Posted on

How to protect Laravel API with Magic Link

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

Prerequisites

Guide with Quickstart: https://magic.link/posts/magic-laravel

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

This guide demonstrates:

  • How to check for a Decentralised ID Token (DIDT) in the Authorization header of an incoming HTTP request.
  • 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.
    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 which will check for and verify a bearer token in the Authorization header of an incoming HTTP request. We'll do that using tools provided by the Magic Laravel SDK.

composer require magiclabs/magic-laravel
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
    ],
];

Enter fullscreen mode Exit fullscreen mode

Get your Magic Secret Key

Sign Up with Magic and get your MAGIC_SECRET_KEY.

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

Dashboard Image

For added security, in Magic's dashboard settings (https://dashboard.magic.link), you can specify the URLs that are allowed to use your live API keys. Test API keys are always allowed to be used on localhost, however, it will block your Live API keys from working anywhere except the URLs specifically added to your allow list.

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


MAGIC_SECRET_API_KEY=sk_test_123456789
MAGIC_RETRIES=
MAGIC_TIMEOUT=
MAGIC_BACKOFF_FACTOR=

Enter fullscreen mode Exit fullscreen mode

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 a middleware to check for a bearer token in an Authorization header for the request and then verify that the token is valid. We'll create that middleware using the make:middleware Artisan command:

php artisan make:middleware CheckDIDT
Enter fullscreen mode Exit fullscreen mode

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);
    }
}


Enter fullscreen mode Exit fullscreen mode

This middleware:

  • Retrieves the Bearer token from the request, and calls the validate function with it 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,
       // …
}
Enter fullscreen mode Exit fullscreen mode

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

// routes/api.php

// 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');

Enter fullscreen mode Exit fullscreen mode

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 for testing, use our Laravel API Authorization template in CodeSandBox.

Update the MAGIC_PUBLISHABLE_KEY

Dashboard Image

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

/* 2️⃣ Initialize Magic Instance */
const magic = new Magic("pk_test_5A2E52658C87281B");
Enter fullscreen mode Exit fullscreen mode

Live Frontend Application 🎉

You 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
Enter fullscreen mode Exit fullscreen mode

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."
}
Enter fullscreen mode Exit fullscreen mode

GET http://localhost:8001/api/public

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" }
Enter fullscreen mode Exit fullscreen mode

GET http://localhost:8001/api/private without Bearer Token

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

GET http://localhost:8001/api/private with Bearer Token

Done 🎉

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

Top comments (0)