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

Bash
01$ git clone https://github.com/magiclabs/example-laravel-api.git
02$ cd example-laravel-api
03$ mv .env.example .env
04$ composer install
05$ php artisan serve

Update .env with your LIVE_SECRET_KEY

01MAGIC_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

    Bash
    01composer create-project laravel/laravel magic-app
    02
    
    03cd magic-app
    04
    
    05php artisan serve

    #Via Laravel Installer

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

    Bash
    01composer global require laravel/installer
    02
    
    03laravel new magic-app
    04
    
    05cd magic-app
    06
    
    07php 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.

01composer 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:

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

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

PHP
01// config/magic.php
02return [
03    /*
04     |-----------------------------------------------------------------------

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

01MAGIC_SECRET_API_KEY=sk_123456789
02MAGIC_RETRIES=
03MAGIC_TIMEOUT=
04MAGIC_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:

01php artisan make:middleware CheckDIDT

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

PHP
01<?php
02// App/Http/Middleware/CheckDIDT.php
03
04namespace App\Http\Middleware;
05
06use Closure;
07use Illuminate\Http\Request;
08use Magic;
09use MagicAdmin\Exception\DIDTokenException;
10use MagicAdmin\Exception\RequestException;
11
12class CheckDIDT
13{
14    /**
15     * Handle an incoming request.
16     *
17     * @param  \Illuminate\Http\Request  $request
18     * @param  \Closure  $next
19     * @return mixed
20     */
21    public function handle(Request $request, Closure $next)
22    {
23
24        $did_token = $request->bearerToken();
25
26        if($did_token){
27
28            try {
29                //verifying the DID Token
30                Magic::token()->validate($did_token);
31                $user = Magic::user()->get_metadata_by_token($did_token);
32                if (!$user) {
33                    return response()->json(["message" => "Unauthorized user"], 401);
34                }
35            } catch (DIDTokenException $e) {
36                return response()->json(["message" => $e->getMessage()], 401);
37            } catch (RequestException $e) {
38                return response()->json(["message" => "Request Exception"], 401);
39            }
40
41        } else {
42            return response()->json(["message" => "Bearer token missing"], 401);
43        }
44
45        return $next($request);
46    }
47}

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:

PHP
01// App/Http/Kernel.php
02// …
03class Kernel extends HttpKernel {
04	// ...
05	protected $routeMiddleware = [
06	    // ...
07	   'didt' => \App\Http\Middleware\CheckDIDT::class,
08	   // …
09}

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

PHP
01// routes/api.php
02use Magic;
03
04// This endpoint does not need authentication.
05Route::get('/public', function (Request $request) {
06    return response()->json(["message" => "Hello from a public endpoint! You don't need to be authenticated to see this."]);
07});
08
09// These endpoints require a valid did token and fetches user's data using did token
10Route::get('/private', function (Request $request) {
11    return response()->json([
12        "message" => "Hello from a private endpoint! You need to have a valid DID Token to see this.",
13        "user" => Magic::user()->get_metadata_by_token($request->bearerToken())->data
14        ]);
15})->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

01/* 2️⃣ Initialize Magic Instance */
02const 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:

01php artisan serve --port=8001

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

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

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
01{ "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:

Javascript
01{
02    "message": "Hello from a private endpoint! You need to have a valid DID Token to see this.",
03    "user": {
04        "email": "[email protected]",
05        "issuer": "did:ethr:0xaa12b334C1f3d……….62367e5B8e",
06        "public_address": "0xaa12b334C1f3d……….62367e5B8e"
07    }
08}

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