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.


$ git clone
$ cd example-laravel-api
$ mv .env.example .env
$ composer install
$ php artisan serve

Update .env with your LIVE_SECRET_KEY



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.

    #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

#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

    '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 Test Application automatically configured for you, or create a new one from your Dashboard.

For added security, in Magic's dashboard settings (, 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.


#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

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

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


            try {
                //verifying the 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 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,
	   // …

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

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


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

#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": "",
        "issuer": "did:ethr:0xaa12b334C1f3d……….62367e5B8e",
        "public_address": "0xaa12b334C1f3d……….62367e5B8e"


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!