Introduction

The API Module makes it quick and easy to access your application and Streams data via a secure public API.

Features

The API module let's you quickly scaffold API applications.

  • Access streams and their entries.
  • Built over Laravel's Passport package.
  • Decorate return values with formatters.
  • Built into existing permissions system.
  • Extend easily with your own API endpoints.

Installation

The API module is a paid addon and requires purchasing from the addon store or an active PRO subscription.

Installing with PRO Subscription

You can install the API module with Composer as a VCS repository if you have an active PRO subscription:

{
     "require": {
        "anomaly/api-module": "~1.0.0"
    },
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/anomalylabs/api-module"
        }
    ]
}

Then simply install for your application:

php artisan addon:install anomaly.module.api
Installing from Store Download

You can install the API module by downloading the addon and placing it within your site's addon directory:

/addons/{application_ref}/anomaly/*

Next, copy the requirements section of the API module's composer.json file into your project's composer.json file:

"require": {
    "laravel/passport": "~1.0.0"
}

Then run composer update to bring in Passport:

php artisan update

Finally install the addon with the addon:install command:

php artisan addon:install anomaly.module.api

Addon Configuration

The API module configures Laravel Passport using it's oauth2.php and api.php config files.

You can override these options by publishing the addon and modifying the resulting configuration file:

php artisan addon:publish anomaly.module.api

The field type will be published to /resources/{application}/addons/anomaly/api-module.

Usage

This section will show you how to use the API module and the endpoints it comes with.

Scopes

Scopes help you lock features of your API. This section will go over how to define and check scopes for your API.

Defining Scopes

The API module defines scopes for Laravel's Passport package using the api.php configuration file.

Simply define your application's available scopes and reference them just as you normally would with Passport.

Please refer to Laravel Passport documentation for more information on scopes.

Checking Scopes

The API module uses the two included Passport middleware to verify that an incoming request is authenticated with a token that has been granted a given scope:

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
Check For All Scopes

The scopes middleware may be assigned to a route to verify that the incoming request's access token has all of the listed scopes:

protected $api = [
    'api/example-module/test' => [
        'uses' => 'Anomaly\ExampleModule\Http\Controller\Api\[email protected]',
        'middleware' => 'scopes:check-status,place-orders'
    ]
];

And with the router:

Route::get('/orders', function () {
    // Access token has both "check-status" and "place-orders" scopes...
})->middleware('scopes:check-status,place-orders');
Check For Any Scopes

The scope middleware may be assigned to a route to verify that the incoming request's access token has at least one of the listed scopes:

protected $api = [
    'api/example-module/test' => [
        'uses' => 'Anomaly\ExampleModule\Http\Controller\Api\[email protected]',
        'middleware' => 'scope:check-status,place-orders'
    ]
];

And with the router:

Route::get('/orders', function () {
    // Access token has either "check-status" or "place-orders" scope...
})->middleware('scope:check-status,place-orders');
Checking Scopes On A Token Instance

Once an access token authenticated request has entered your application, you may still check if the token has a given scope using the tokenCan method on the authenticated User instance:

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('place-orders')) {
        //
    }
});

Issuing Tokens

This section will provide a brief overview of how to create access tokens for your API.

Laravel Passport handles all of the client and token management for the API module. Please refer to Laravel docs for more detailed information on how to manage clients and issue tokens.

Issuing Access Tokens

This section will show you how to quickly issue access tokens for your API.

Creating a Client

To begin we first need to create our API client:

php artisan passport:client

Provide the information requested about your client and take note of the client ID and secret provided. For this example we will assume your callback URL is http://workbench.local:8888/callback.

Note: The user specified for the client will provide the permissions level for tokens associated with this client.
Create the Callback

Next we need to create the redirect URL we specified in the above step. In this case we're assuming it's http://workbench.local:8888/callback so let's open our routes/web.php file and copy the following code into it:

Route::get(
    '/callback',
    function (\Illuminate\Http\Request $request) {
        $http = new GuzzleHttp\Client;

        $response = $http->post(
            'http://workbench.local:8888/oauth/token',
            [
                'form_params' => [
                    'grant_type'    => 'authorization_code',
                    'client_id'     => CLIENT_ID,
                    'client_secret' => CLIENT_SECRET,
                    'redirect_uri'  => 'http://workbench.local:8888/callback',
                    'code'          => $request->code,
                ],
            ]
        );

        return json_decode((string)$response->getBody(), true);
    }
);
Requesting a Token

To get request a token open your browser and login to your application.

Next, navigate to /oauth/request?client=CLIENT_ID and accept the authorization request.

You will be redirected to the callback URL we just created and you should see a token and refresh token displayed on your screen. Store the tokens in your consuming application and start making API requests!

Issuing Password Grant Tokens

This section will show you how to quickly issue password grant access tokens for your API.

Creating a Client

To begin we first need to create our API client with the --password option:

php artisan passport:client --password
Requesting a Token

To request a password grant token you must make a POST request to /oauth/tokens. With Guzzle that might look something like this:

$http = new GuzzleHttp\Client;

$response = $http->post(
    'http://yoursite.com/oauth/token',
    [
        'form_params' => [
            'grant_type'    => 'password',
            'client_id'     => CLIENT_ID,
            'client_secret' => CLIENT_SECRET,
            'username'      => USER_EMAIL,
            'password'      => USER_PASSWORD,
        ],
    ]
);

return json_decode((string)$response->getBody(), true);

The response will contain your access and refresh tokens. Store the tokens in your consuming application and start making API requests!

Note: The user credentials provided will determine the permissions level for the token returned.

Issuing Personal Access Tokens

This section will show you how to quickly issue personal access tokens for your API.

Creating a Client

To begin we first need to create our API client with the --personal option:

php artisan passport:client --personal
Creating a Token

To create a personal access token you can open up Tinker:

php artisan tinker

And run the following after replacing the variables with your desired ID and name:

app(\Anomaly\ApiModule\User\UserModel::class)->find($id)->createToken($tokenName)->accessToken;
Note: The user specified will provide the permissions level for the access token.

API Access

This section will go over accessing your API.

Authorizing Requests

API routes are protected by Passport. API consumers should specify their access token as a Bearer token in the Authorization header of their request.

For example, when using the Guzzle HTTP library:

$response = $client->request('GET', '/api/entries/pages/pages', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer ' . $accessToken,
    ],
]);

API Routes

This section will go over everything you need to know about API routes.

/api/entries/{namespace}/{stream}

The entries endpoint exposes Stream entries specified by the namespace and stream parameters.

Parameters
Key Required Type Example Description

namespace

true

string

pages

The namespace of the Stream you want to access entries for.

stream

true

string

pages

The slug of the Stream you want to access entries for within the provided namespace.

/api/entries/{namespace}/{stream}/{id}

The entries endpoint also exposes single Stream entries specified by the namespace, stream, and id parameters.

Parameters
Key Required Type Example Description

namespace

true

string

pages

The namespace of the Stream you want to access entries for.

stream

true

string

pages

The slug of the Stream you want to access entries for within the provided namespace.

id

true

integer

10

The ID of the entry you want to access within the provided namespace and stream.

Traversing Relationships

You can use a RESTful pattern to traverse entry relationships and format the results too:

For example to get the category for the post with ID 1:

/api/entries/posts/posts/1/category

To get the user that created the above category:

/api/entries/posts/posts/1/category/created_by

Defining API Routes

Defining custom API routes could not be easier. Let's take a look at a couple ways to handle this.

Addon Service Providers

You can define your API routes just like normal routes in the protected $api = [] property of your addon service provider:

protected $api = [
    'api/example-module/test' => 'Anomaly\ExampleModule\Http\Controller\Api\[email protected]'
];
Laravel Router

You can also define routes the Laravel way using the Router. Just make sure to define the auth:api middleware:

Router::get('api/example-module/test', 'Anomaly\ExampleModule\Http\Controller\Api\[email protected]')
    ->middleware('auth:api');
The Resource Controller

Your API controllers should extend the base resource controller:

<?php namespace Anomaly\ExampleModule\Http\Controller\Api;

use Anomaly\Streams\Platform\Http\Controller\ResourceController;

class TestController extends ResourceController
{

}

Formatters

The formatters query string parameter is an array of attribute and pattern formatters.

Formatters are based on field type presenters and provide a simple dot notation syntax.

This section will show you how to decorate your returned data with formatters.

Decorating Existing Fields

To decorate an existing field simply provide a formatter named the same name as the field in the Stream entry.

For example to decorate the email field in Users with EmailFieldType::mailto() method you would specify a formatter like this:

$http = new GuzzleHttp\Client;

$response = $http->request(
    'GET',
    'http://yoursite.com/entries/users/users',
    [
        'query' => [
            'formatters' => [
                'email' => 'entry.email.mailto'
            ]
        ],
        'headers' => [
            'Authorization'    => 'Bearer ' . $accessToken,
        ],
    ]
);

return json_decode((string)$response->getBody(), true);

The response would be formatted like this:

{
  "data": [
    {
      "id": 1,
      "sort_order": 1,
      "created_at": "2017-02-25 12:40:06",
      "created_by_id": null,
      "updated_at": "2017-02-25 14:09:29",
      "updated_by_id": 1,
      "deleted_at": null,
      "email": "<a href=\"&#109;&#x61;&#x69;&#x6c;&#x74;&#111;&#58;&#x72;ya&#110;&#64;p&#x79;&#114;&#111;&#x63;&#109;s&#x2e;&#x63;om\">&#x72;ya&#110;&#64;p&#x79;&#114;&#111;&#x63;&#109;s&#x2e;&#x63;om</a>",
      "username": "admin",
      "display_name": "Administrator",
      "first_name": null,
      "last_name": null,
      "activated": 1,
      "enabled": 1,
      "permissions": null,
      "last_login_at": "2017-02-25 14:09:29",
      "remember_token": "AT5Y59ugouhS3RONVf3KgcxjBNoPG2CQKJTywyYQ31C1uNw7yKZJYIycPOsr",
      "activation_code": null,
      "reset_code": null,
      "last_activity_at": "2017-02-25 14:06:43",
      "ip_address": "::1"
    },
    {
      "id": 2,
      "sort_order": 2,
      "created_at": "2017-02-25 12:40:07",
      "created_by_id": null,
      "updated_at": "2017-02-25 12:40:07",
      "updated_by_id": null,
      "deleted_at": null,
      "email": "<a href=\"&#109;&#97;&#x69;&#108;&#x74;&#111;:&#x64;&#101;m&#x6f;&#x40;&#x70;y&#x72;o&#x63;&#109;&#115;&#46;&#99;&#x6f;&#x6d;\">&#x64;&#101;m&#x6f;&#x40;&#x70;y&#x72;o&#x63;&#109;&#115;&#46;&#99;&#x6f;&#x6d;</a>",
      "username": "demo",
      "display_name": "Demo User",
      "first_name": null,
      "last_name": null,
      "activated": 1,
      "enabled": 1,
      "permissions": null,
      "last_login_at": null,
      "remember_token": null,
      "activation_code": null,
      "reset_code": null,
      "last_activity_at": null,
      "ip_address": null
    }
  ],
  "pagination": {
    "total": 2,
    "per_page": 15,
    "current_page": 1,
    "last_page": 1,
    "next_page_url": null,
    "prev_page_url": null,
    "from": 1,
    "to": 2
  }
}

Adding Additional Fields

Formatters can also be used to include "new fields" in the return data.

If we wanted to include the mailto link but not replace the email attribute with it then we could define the formatter with a new name:

$http = new GuzzleHttp\Client;

$response = $http->request(
    'GET',
    'http://yoursite.com/entries/users/users',
    [
        'query' => [
            'formatters' => [
                'mailto' => 'entry.email.mailto'
            ]
        ],
        'headers' => [
            'Authorization'    => 'Bearer ' . $accessToken,
        ],
    ]
);

return json_decode((string)$response->getBody(), true);

The response would be formatted like this:

{
  "data": [
    {
      "id": 1,
      "sort_order": 1,
      "created_at": "2017-02-25 12:40:06",
      "created_by_id": null,
      "updated_at": "2017-02-25 14:09:29",
      "updated_by_id": 1,
      "deleted_at": null,
      "email": "[email protected]",
      "username": "admin",
      "display_name": "Administrator",
      "first_name": null,
      "last_name": null,
      "activated": 1,
      "enabled": 1,
      "permissions": null,
      "last_login_at": "2017-02-25 14:09:29",
      "remember_token": "AT5Y59ugouhS3RONVf3KgcxjBNoPG2CQKJTywyYQ31C1uNw7yKZJYIycPOsr",
      "activation_code": null,
      "reset_code": null,
      "last_activity_at": "2017-02-25 14:06:43",
      "ip_address": "::1",
      "mailto": "<a href=\"&#109;&#x61;&#x69;&#x6c;&#x74;&#111;&#58;&#x72;ya&#110;&#64;p&#x79;&#114;&#111;&#x63;&#109;s&#x2e;&#x63;om\">&#x72;ya&#110;&#64;p&#x79;&#114;&#111;&#x63;&#109;s&#x2e;&#x63;om</a>"
    },
    {
      "id": 2,
      "sort_order": 2,
      "created_at": "2017-02-25 12:40:07",
      "created_by_id": null,
      "updated_at": "2017-02-25 12:40:07",
      "updated_by_id": null,
      "deleted_at": null,
      "email": "[email protected]",
      "username": "demo",
      "display_name": "Demo User",
      "first_name": null,
      "last_name": null,
      "activated": 1,
      "enabled": 1,
      "permissions": null,
      "last_login_at": null,
      "remember_token": null,
      "activation_code": null,
      "reset_code": null,
      "last_activity_at": null,
      "ip_address": null,
      "mailto": "<a href=\"&#109;&#97;&#x69;&#108;&#x74;&#111;:&#x64;&#101;m&#x6f;&#x40;&#x70;y&#x72;o&#x63;&#109;&#115;&#46;&#99;&#x6f;&#x6d;\">&#x64;&#101;m&#x6f;&#x40;&#x70;y&#x72;o&#x63;&#109;&#115;&#46;&#99;&#x6f;&#x6d;</a>"
    }
  ],
  "pagination": {
    "total": 2,
    "per_page": 15,
    "current_page": 1,
    "last_page": 1,
    "next_page_url": null,
    "prev_page_url": null,
    "from": 1,
    "to": 2
  }
}

Including Related Entries

You can use formatters to include related entries in the same request:

$response = $http->request(
    'GET',
    'http://workbench.local:8888/api/entries/users/users',
    [
        'query' => [
            'formatters' => [
                'roles' => 'entry.roles'
            ]
        ],
        'headers' => [
            'Authorization'    => 'Bearer ' . $accessToken,
        ],
    ]
);

return json_decode((string)$response->getBody(), true);

The response data will include the collection of roles:

{
  "data": [
    {
      "id": 1,
      "sort_order": 1,
      "created_at": "2017-02-25 12:40:06",
      "created_by_id": null,
      "updated_at": "2017-02-25 14:09:29",
      "updated_by_id": 1,
      "deleted_at": null,
      "email": "[email protected]",
      "username": "admin",
      "display_name": "Administrator",
      "first_name": null,
      "last_name": null,
      "activated": 1,
      "enabled": 1,
      "permissions": null,
      "last_login_at": "2017-02-25 14:09:29",
      "remember_token": "AT5Y59ugouhS3RONVf3KgcxjBNoPG2CQKJTywyYQ31C1uNw7yKZJYIycPOsr",
      "activation_code": null,
      "reset_code": null,
      "last_activity_at": "2017-02-25 14:06:43",
      "ip_address": "::1",
      "roles": {
        "admin": {
          "id": 1,
          "sort_order": 1,
          "created_at": "2017-02-25 12:40:06",
          "created_by_id": null,
          "updated_at": null,
          "updated_by_id": null,
          "deleted_at": null,
          "slug": "admin",
          "permissions": null,
          "name": "Admin",
          "description": "The super admin role."
        }
      }
    },
    {
      "id": 2,
      "sort_order": 2,
      "created_at": "2017-02-25 12:40:07",
      "created_by_id": null,
      "updated_at": "2017-02-25 12:40:07",
      "updated_by_id": null,
      "deleted_at": null,
      "email": "[email protected]",
      "username": "demo",
      "display_name": "Demo User",
      "first_name": null,
      "last_name": null,
      "activated": 1,
      "enabled": 1,
      "permissions": null,
      "last_login_at": null,
      "remember_token": null,
      "activation_code": null,
      "reset_code": null,
      "last_activity_at": null,
      "ip_address": null,
      "roles": {
        "user": {
          "id": 2,
          "sort_order": 2,
          "created_at": "2017-02-25 12:40:06",
          "created_by_id": null,
          "updated_at": null,
          "updated_by_id": null,
          "deleted_at": null,
          "slug": "user",
          "permissions": null,
          "name": "User",
          "description": "The default user role."
        }
      }
    }
  ],
  "pagination": {
    "total": 2,
    "per_page": 15,
    "current_page": 1,
    "last_page": 1,
    "next_page_url": null,
    "prev_page_url": null,
    "from": 1,
    "to": 2
  }
}