How to protect Laravel API with Magic Link
Tags: laravel, php, api, magic
Published: 2021-01-14 18:56:15

A tutorial to demonstrate how to add authorization to a Laravel API with [Magic's Laravel Plugin](https://github.com/magiclabs/magic-laravel).

## Prerequisites
* [Composer](http://getcomposer.org/)
* [PHP]((https://www.php.net/downloads.php))
* [Laravel](https://laravel.com/)
* [Magic Laravel SDK](https://github.com/magiclabs/magic-laravel)

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

    ```bash
    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:

    ```bash
    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.

```bash
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:

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

After the file is generated, it will be located at `config/magic.php`. 
```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](https://dashboard.magic.link/signup) 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](https://dashboard.magic.link/app/all_apps).

![Dashboard Image](https://dev-to-uploads.s3.amazonaws.com/i/fnjqvscslu11ih87p94t.png)

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.

```text

MAGIC_SECRET_API_KEY=sk_test_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 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:

```bash
php artisan make:middleware CheckDIDT
```

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

```php
<?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 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`:

```php
// 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:

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

```

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](https://codesandbox.io/s/boring-fog-laravel-didt-x6b6x) in CodeSandBox.

## Update the MAGIC_PUBLISHABLE_KEY

![Dashboard Image](https://dev-to-uploads.s3.amazonaws.com/i/fnjqvscslu11ih87p94t.png)

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

```javascript
/* 2️⃣ Initialize Magic Instance */
const magic = new Magic("pk_test_5A2E52658C87281B");
```

## 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:

```bash
php artisan serve --port=8001
```

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

```javascript
{
    "message": "Hello from a public endpoint! You don't need to be authenticated to see this."
}
```
![GET http://localhost:8001/api/public](https://dev-to-uploads.s3.amazonaws.com/i/hvoef2s6y09loe0dmdwc.png)

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:

```javascript
{ "message": "Bearer token missing" }
```

![GET http://localhost:8001/api/private without Bearer Token](https://dev-to-uploads.s3.amazonaws.com/i/mv5euab219ql5g8z1xdl.png)

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:

```javascript
{
    "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"
    }
}
```

![GET http://localhost:8001/api/private with Bearer Token](https://dev-to-uploads.s3.amazonaws.com/i/vehxyrewsh22w5fasdan.png)

# Done 🎉

Congratulations! You have successfully secured your Laravel API with [Magic](https://magic.link).