This forum has moved to a new location and is in read-only mode. Please visit talk.octobercms.com to access the new location.

Mohsin
Mohsin

A year ago csavelief7092 asked how to do this and I gave a response here. It is not the best solution and I’ve come across a better way to solve this. I’ve been reading on resource controllers and that is Laravel’s way of building RESTful APIs. So, I tried the same in OctoberCMS but realised that controllers work a little differently here. This guide should help those who want to build RESTful APIs.

So you need to scaffold a plugin for the REST service (or use existing plugins by extending it and adding the RESTful stuff). Make a routes.php. Here’s a sample:

<?php
Route::group(['prefix' => 'api/v1'], function () {
    Route::resource('someroute', 'Acme\User\Controllers\MyController');
});

And this would be accessible from YOUR_URL/api/v1/someroute. Based on the HTTP request verb different actions would be called i.e. index, create, store, etc. You can see the list in the table here. The easy way of doing this is to create a controller MyController which extends Illuminate\Routing\Controller like this:

Contents of FooController.php (I create this in the usual plugin controller folder, you can create in separate folder if you want to)

<?php namespace Acme\Plugin\Controllers;

use Illuminate\Routing\Controller;

/**
 * Sample Resourceful Test Controller
 */
class FooController extends Controller
{
    public function index()
    {
        // Replace with logic to return the model data
        return 'bar (index)';
    }

    public function store()
    {
        // Replace with logic to store/create the model. use post(‘varname’) to get values.
        return 'bar (store)';
    }
}

The route to add in the Route group for this is:

Route::resource('foo', 'Acme\Plugin\Controllers\FooController');

And this works perfectly well, you can write separate controllers for API classes. But I wanted to re-use OctoberCMS’s existing controller functions i.e. the Backend Controllers to add the RESTful components. So here’s what I did…

So assuming I scaffolded a plugin controller called Foo using php artisan create:controller Acme.Plugin Foo then inside Foo.php I tried adding index() but that broke the backend ListController. So, I added a few functions to enable the existing controller to allow RESTful API actions:

<?php namespace Acme\Plugin\Controllers;

use BackendMenu;
use Backend\Classes\Controller;

/**
 * Foo Back-end Controller
 */
class Foo extends Controller
{
    public $implement = [
        'Backend.Behaviors.FormController',
        'Backend.Behaviors.ListController'
    ];

    public $formConfig = 'config_form.yaml';
    public $listConfig = 'config_list.yaml';

    public function __construct()
    {
        parent::__construct();
        BackendMenu::setContext('Acme.Plugin', 'plugin', 'foo');
    }

    public function apiIndex()
    {
      return 'bar (index)';
    }

    public function apiFoo()
    {
      return 'bar (foo)';
    }

    /* Functions to allow RESTful actions */
    public static function getAfterFilters() {return [];}
    public static function getBeforeFilters() {return [];}
    public static function getMiddleware() {return [];}
    public function callAction($method, $parameters=false) {
      $action = 'api' . ucfirst($method);
      if (method_exists($this, $action) && is_callable(array($this, $action)))
      {
        return call_user_func_array(array($this, $action), $parameters);
      } else {
        return response()->json([
            'message' => 'Not Found',
        ], 404);
      }
    }
}

So now I’m able to define the routes.php in the same way and at the same time allow using the existing controllers to build RESTful components to them. This is useful because for most use cases I would only be delivering the same models that are accessible to backend users thro’ backend controllers to my mobile users too (or any service that consumes the RESTful API). The only difference would be the level of access I’d give to the users on what they can view, edit or delete and these apiAction calls help solve that purpose without disturbing the existing backend controller’s actions.

You may have noticed that I’ve added an Foo action in the list even though it’s not a standard RESTful action that laravel provides. Sometimes, you might need custom actions so to do that you just have to add that action in the routes file before adding the Route::resource function. Like this…

Route::get(‘foo/foo', ‘Acme\Plugin\Controllers\Foo@foo');
Route::resource(‘foo', ‘Acme\ Plugin\Controllers\Foo');

So, now I’ve defined that foo action is accessible when the request comes with the GET HTTP verb and the second line does the usual i.e. gives access to the standard HTTP RESTful resource verbs that Laravel provides.

I hope this guide is accurate. For example, I am unsure about the Functions to allow RESTful actions as to whether it affects the backend ListController or FormController in any way but I hope not. I think it may be a good idea to add a RestController backend behaviour as public $implement = ['Backend.Behaviors.RestController']; in OctoberCMS core that automatically adds the functionality I just described (it could work in a way that it takes all functions that start with api as RESTful actions) so I don’t need to paste these functions in each controller which I wish to expose as a RESTful one. Hope the guide helps, do comment if you have corrections or suggestions.

chris10207
chris10207

Hey Moshin, very nice ! Im also currently developing a RESTFUL Api with October. 1st thing is that i decided to separate my RESTFul Controllers from the standard backend October Controllers to follow the single responsibility principles. I ddint want to mix them up. So I have a specific namespace Http/Controller to hold the API Controllers. Therefore im not extend the OC Controller but a custom ApiController whose responsibility is to provide all the different methods for Responding. responsibility of the controllers would be then to validate the entry data (using the validation coming from the OC models is easy), get the data from the database (using Eloquent), use a data transformer and respond to the client.

Mohsin
Mohsin

This is good input… I too had considered separating the RESTful controllers in separate folders, initially. That’s why I said

I create this in the usual plugin controller folder, you can create in separate folder if you want to.

And I usually don’t extend the controllers unless it’s in an external plugin such as User plugin, Blog plugin, etc. For my own plugins I wrote the RESTful logic right there on the existing plugin controllers.

Validation is easy indeed, all that is needed to be done is to create an instance of the Validator class.

ListController and FormController behaviour are defined in the same controller so I don’t think that follows the single responsibly rule well either, that is why I went ahead and put the code there itself. The way OctoberCMS team had added the ImportExportController as a behaviour to be used in existing backend controllers rather than defined it in a separate space suggested that I can re-use the same controller and it made sense since the related model is already loaded in that controller.

As for data transformer, could you tell me what do you currently use, is it a package like ThePhpLeague’s Fractal package? And have you faced any issues in using it?

Last updated

chris10207
chris10207

I found October really performant to write a RESTFUL Api indeed. The import-export, list and form behaviours are specific OC backend features, this is why i m not mixing this up in my REST Controllers. Probably i missed and dont see something here, could you tell if you are using these 3 behaviours in REST context ? Also, separating the 2 concerns help me to not create bugs when i change the code in either of one the controller ( backend or api). Since we are writing a REST controller for a specific resource, we already know which model we are gonna use, so i dont see the 'related model already loaded' as a bonus here. Yes, Fractal is definitely the package to go with. Right now i coded my own data transformers to understand and grasp the principles behind and will migrate to Fractal probably next week.

chris10207
chris10207

Another important subject is the authentication, i tried to implement an oauth2 packages without success, get stucked at some stage. Anyone manage to implement a JWT authentication with OC ?

Mohsin
Mohsin

chris10207 said:

Another important subject is the authentication, i tried to implement an oauth2 packages without success, get stucked at some stage. Anyone manage to implement a JWT authentication with OC ?

Well I did. And it works.

Last updated

chris10207
chris10207

Nice Mohsin. Which package did you use for the JWT ? i get my hands on this one https://github.com/tymondesigns/jwt-auth/ but so far i have problem with the AuthManager :/

planetadeleste
planetadeleste

chris10207 said:

Nice Mohsin. Which package did you use for the JWT ? i get my hands on this one https://github.com/tymondesigns/jwt-auth/ but so far i have problem with the AuthManager :/

I have similar problem, can't authenticate. I get Class auth does not exist.

@Mohsin, can you share your code implementation of JWT with OC?

Thanks

chris10207
chris10207

Hi planeta, here is the part of the code i am using inside the plugin.php file. sorry it is a mess and reflect all my testing and trying to make it works, hope that can help anyway

// $alias->alias('Auth', 'RainLab\User\Facades\Auth');
        // App::singleton('auth', function() {
        //     // return \RainLab\User\Classes\AuthManager::instance();
        // });

        // App::singleton('auth', function ($app) {
        //     return new \Illuminate\Auth\AuthManager($app);// return $app->make('auth');
        // });

        // our JWT service provider
        // App::register('Dingo\Api\Provider\LaravelServiceProvider');
        // $alias->alias('JWTDispatcher', 'Dingo\Api\Facade\API');
        // $alias->alias('JWTRoute', 'Dingo\Api\Facade\Route');

        // our JWT service provider
        // App::register('Tymon\JWTAuth\Providers\JWTAuthServiceProvider');// App::register('Tymon\JWTAuth\Providers\LaravelServiceProvider');
        // $alias->alias('JWTAuth', 'Tymon\JWTAuth\Facades\JWTAuth');
        // $alias->alias('JWTFactory', 'Tymon\JWTAuth\Facades\JWTFactory');

        // our oauth2 service provier
        // App::register('\Dingo\Api\Provider\LaravelServiceProvider');
        // App::register('\LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider');
        // App::register('\LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider');
        // $alias->alias('Authorizer', 'LucaDegasperi\OAuth2Server\Facades\Authorizer');

        // Register middleware
          // $this->app['Illuminate\Contracts\Http\Kernel']->pushMiddleware('\LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware');

        // Register our route middlewares
        // $this->app['router']->middleware('oauth', '\LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware');
        // $this->app['router']->middleware('oauth-user', '\LucaDegasperi\OAuth2Server\Middleware\OAuthUserOwnerMiddleware');
        // $this->app['router']->middleware('oauth-client', '\LucaDegasperi\OAuth2Server\Middleware\OAuthClientOwnerMiddleware');
        // $this->app['router']->middleware('check-authorization-params', '\LucaDegasperi\OAuth2Server\Middleware\CheckAuthCodeRequestMiddleware');
planetadeleste
planetadeleste

Thanks @chris10207

I could pass the Class auth does not exist exception. Now I have two problems.
If I use this:

    App::singleton('auth', function() {
        return \RainLab\User\Classes\AuthManager::instance();
    });

Get this error:

Type error: Argument 1 passed to Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter::__construct() must be an instance of Illuminate\Auth\AuthManager, instance of RainLab\User\Classes\AuthManager given

And if use this:

    App::singleton('auth', function ($app) {
        return new \Illuminate\Auth\AuthManager($app);
    });

I get this:

Missing argument 1 for Illuminate\Auth\AuthManager::createDriver(), called in /home/recibosya/public_html/vendor/laravel/framework/src/Illuminate/Support/Manager.php on line 87 and defined

Continue testing....

Thanks

planetadeleste
planetadeleste

Ok, I found the solution.
To solve the problem, is needed the driver auth config. This file doesn't exists in OC installation and need to created at /config/auth.php

Here is my code.

// plugins/acme/demo/Plugin.php
class Plugin extends PluginBase
{
    public function boot()
    {
        // Register ServiceProviders
        App::register(\Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class);

        // Register aliases
        $alias = AliasLoader::getInstance();
        $alias->alias('JWTAuth', \Tymon\JWTAuth\Facades\JWTAuth::class);
        $alias->alias('JWTFactory', \Tymon\JWTAuth\Facades\JWTFactory::class);

         App::singleton('auth', function ($app) {
             return new \Illuminate\Auth\AuthManager($app);
         });

        $this->app['router']->middleware('jwt.auth', '\Tymon\JWTAuth\Middleware\GetUserFromToken');
        $this->app['router']->middleware('jwt.refresh', '\Tymon\JWTAuth\Middleware\RefreshToken');

    }
}

// plugins/acme/demo/routes.php
Route::group(['prefix' => 'api'], function()
{
    Route::post('auth/login', '\Acme\Demo\Controllers\ApiAuth@authenticate');
});

//plugins/acme/demo/controllers/ApiAuth.php
namespace Acme\Demo\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Tymon\JWTAuth\Exceptions\JWTException;
use JWTAuth;

class ApiAuth extends Controller
{
    public function authenticate(Request $request)
    {
        $credentials = $request->only('username', 'password');
        try {
            // verify the credentials and create a token for the user
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            // something went wrong
            return response()->json(['error' => 'could_not_create_token'], 500);
        }
        // if no errors are encountered we can return a JWT
        return response()->json(compact('token'));
    }
}

// /config/auth.php
return [

    /*
    |--------------------------------------------------------------------------
    | Default Authentication Driver
    |--------------------------------------------------------------------------
    |
    | This option controls the authentication driver that will be utilized.
    | This driver manages the retrieval and authentication of the users
    | attempting to get access to protected areas of your application.
    |
    | Supported: "database", "eloquent"
    |
    */

    'driver' => 'database',

    /*
    |--------------------------------------------------------------------------
    | Authentication Model
    |--------------------------------------------------------------------------
    |
    | When using the "Eloquent" authentication driver, we need to know which
    | Eloquent model should be used to retrieve your users. Of course, it
    | is often just the "User" model but you may use whatever you like.
    |
    */

    'model' => \Rainlab\User\Models\User::class,

    /*
    |--------------------------------------------------------------------------
    | Authentication Table
    |--------------------------------------------------------------------------
    |
    | When using the "Database" authentication driver, we need to know which
    | table should be used to retrieve your users. We have chosen a basic
    | default value but you may easily change it to any table you like.
    |
    */

    'table' => 'users',

    /*
    |--------------------------------------------------------------------------
    | Password Reset Settings
    |--------------------------------------------------------------------------
    |
    | Here you may set the options for resetting passwords including the view
    | that is your password reset e-mail. You can also set the name of the
    | table that maintains all of the reset tokens for your application.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'password' => [
        'email' => 'emails.password',
        'table' => 'password_resets',
        'expire' => 60,
    ],

];
funteapot15611
funteapot15611

planetadeleste said:

Ok, I found the solution.

Hi, which version JWT do you setup? And how to install the JWT? Thanks.

I found https://github.com/gpasztor87/oc-jwt-auth but the php artisan jwt:generate will failed.

https://github.com/tymondesigns/jwt-auth I don't know how to do in OctoberCMS

Last updated

planetadeleste
planetadeleste

@funteapot15611, In your plugin folder (/plugins/{PluginName}), create a composer.json file, using composer init command, them add composer require tymon/jwt-auth:0.5.*.

Them, just follow my post.

After that, you can run php artisan jwt:generate

funteapot15611
funteapot15611

@planetadeleste, thanks your reply. I do as what you said. I make a composer.json in /plugins/leadrive/api, my plugin's name is api.


{
    "name": "leadrive/api",
    "description": "JSON Web Token Authentication plugin for OctoberCMS",
    "keywords": ["october", "cms", "octobercms", "api", "authentication", "json web token", "jwt"],
    "require": {
        "tymon/jwt-auth": "0.5.*"
    },
    "minimum-stability": "dev"
}

After run composer install, I add these code in Plugin.php.


use App;
use Illuminate\Foundation\AliasLoader; 

Finally, run php artisan jwt:generate successful.

I want to make a Backend Auth, My auth.php is


    'model' => \Backend\Models\User::class,
    'table' => 'backend_users',

After run, in the ApiAuth,

if (! $token = JWTAuth::attempt($credentials)) {

It will report error "Class App\User does not exist"

Last updated

planetadeleste
planetadeleste

Maybe need to change the user config in /config/jwt.php file.
I have this:

  /*
    |--------------------------------------------------------------------------
    | User Model namespace
    |--------------------------------------------------------------------------
    |
    | Specify the full namespace to your User model.
    | e.g. 'Acme\Entities\User'
    |
    */

    'user' => '\RainLab\User\Models\User',
funteapot15611
funteapot15611

Thank you, Yes, I forgot the jwt.php file. Now it work ok.

chris10207
chris10207

very cool planetadeleste and thanks

mano
mano

planetadeleste said:

@funteapot15611, In your plugin folder (/plugins/{PluginName}), create a composer.json file, using composer init command, them add composer require tymon/jwt-auth:0.5.*.

Them, just follow my post.

After that, you can run php artisan jwt:generate

so After following your post i was successfully able to generate jwt token . where do i have to place this token now ?

mano
mano

planetadeleste said:

Ok, I found the solution.
To solve the problem, is needed the driver auth config. This file doesn't exists in OC installation and need to created at /config/auth.php

Here is my code.

// plugins/acme/demo/Plugin.php
class Plugin extends PluginBase
{
   public function boot()
   {
       // Register ServiceProviders
       App::register(\Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class);

       // Register aliases
       $alias = AliasLoader::getInstance();
       $alias->alias('JWTAuth', \Tymon\JWTAuth\Facades\JWTAuth::class);
       $alias->alias('JWTFactory', \Tymon\JWTAuth\Facades\JWTFactory::class);

        App::singleton('auth', function ($app) {
            return new \Illuminate\Auth\AuthManager($app);
        });

       $this->app['router']->middleware('jwt.auth', '\Tymon\JWTAuth\Middleware\GetUserFromToken');
       $this->app['router']->middleware('jwt.refresh', '\Tymon\JWTAuth\Middleware\RefreshToken');

   }
}

// plugins/acme/demo/routes.php
Route::group(['prefix' => 'api'], function()
{
   Route::post('auth/login', '\Acme\Demo\Controllers\ApiAuth@authenticate');
});

//plugins/acme/demo/controllers/ApiAuth.php
namespace Acme\Demo\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Tymon\JWTAuth\Exceptions\JWTException;
use JWTAuth;

class ApiAuth extends Controller
{
   public function authenticate(Request $request)
   {
       $credentials = $request->only('username', 'password');
       try {
           // verify the credentials and create a token for the user
           if (! $token = JWTAuth::attempt($credentials)) {
               return response()->json(['error' => 'invalid_credentials'], 401);
           }
       } catch (JWTException $e) {
           // something went wrong
           return response()->json(['error' => 'could_not_create_token'], 500);
       }
       // if no errors are encountered we can return a JWT
       return response()->json(compact('token'));
   }
}

// /config/auth.php
return [

   /*
   |--------------------------------------------------------------------------
   | Default Authentication Driver
   |--------------------------------------------------------------------------
   |
   | This option controls the authentication driver that will be utilized.
   | This driver manages the retrieval and authentication of the users
   | attempting to get access to protected areas of your application.
   |
   | Supported: "database", "eloquent"
   |
   */

   'driver' => 'database',

   /*
   |--------------------------------------------------------------------------
   | Authentication Model
   |--------------------------------------------------------------------------
   |
   | When using the "Eloquent" authentication driver, we need to know which
   | Eloquent model should be used to retrieve your users. Of course, it
   | is often just the "User" model but you may use whatever you like.
   |
   */

   'model' => \Rainlab\User\Models\User::class,

   /*
   |--------------------------------------------------------------------------
   | Authentication Table
   |--------------------------------------------------------------------------
   |
   | When using the "Database" authentication driver, we need to know which
   | table should be used to retrieve your users. We have chosen a basic
   | default value but you may easily change it to any table you like.
   |
   */

   'table' => 'users',

   /*
   |--------------------------------------------------------------------------
   | Password Reset Settings
   |--------------------------------------------------------------------------
   |
   | Here you may set the options for resetting passwords including the view
   | that is your password reset e-mail. You can also set the name of the
   | table that maintains all of the reset tokens for your application.
   |
   | The expire time is the number of minutes that the reset token should be
   | considered valid. This security feature keeps tokens short-lived so
   | they have less time to be guessed. You may change this as needed.
   |
   */

   'password' => [
       'email' => 'emails.password',
       'table' => 'password_resets',
       'expire' => 60,
   ],

];

what about the jwt token , publickey , privatekey etc where should we place this

jonathandey
jonathandey

Hi,

I have just this moment published a plugin that implements the dingo/api package. It is very developer focused. Currently it only supports JWT for authentication.

Add it to your site/project, run composer install then create a routes file (see the routes.example.php) in your plugins directory.

The plugin can be found here: https://github.com/jonathandey/oc-jd-dingoapi

Hope it helps!

1-20 of 25

You cannot edit posts or make replies: the forum has moved to talk.octobercms.com.