7

I'm trying to create native mobile app for Magento 2 website. It seems there is rest API in Magento 2 already. However, they don't seem to be designed for mobile app since most of essential APIs are not accessible from normal customer account (e.g. GET /V1/products requires Magento_Catalog::products)

I think there are two options to handle this problem.

  1. Create admin account that has all permissions required, and use the token for non-anonymous APIs: This seems simple, but there is potential security issue that malicious user may acquire the admin token and use it.

  2. Define own API set, and add classes and functions which is safe to be exposed to customer: This needs more work apparently.

I wonder if there's better option that i'm missing. And, I also wonder what is the purpose of Rest API even when customers cannot use those APIs.

2

4 Answers 4

9

In order to create a rest api there are some certain requirements

  1. you need to create an interface in your modules Api folder.
  2. then you need to define all the api methods that you want to expose to the web in the interface.
  3. All the methods should have a doc-block.
  4. in the doc-block @api must be defined
  5. if your method expects parameters than all the parameters must be defined in the doc-block as @params
  6. return type of the method must be defined as @return 7.concrete class of the method must be defined and it should give the definition of all the api methods and should inherit the doc-block.

If your api method do not met any of the above requirements then rest api will not work .

Now for an example lets create a test module named Webkul_TestApi for better understanding :

create your module composer.json, regitration.php and module.xml files:

<?php
/**
* Webkul Software.
*
* @category Webkul_MpApi
*
* @author Webkul
* @copyright Copyright (c) 2010-2016 Webkul Software Private Limited (https://webkul.com)
* @license https://store.webkul.com/license.html
*/
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Webkul_TestApi',
    __DIR__
);
{
    "name": "webkul/marketplace-api",
    "description": "Marketplace api for magento2",
    "require": {
        "php": "~5.5.0|~5.6.0|~7.0.0",
        "magento/module-config": "100.0.*",
        "magento/module-store": "100.0.*",
        "magento/module-checkout": "100.0.*",
        "magento/module-catalog": "100.0.*",
        "magento/module-sales": "100.0.*",
        "magento/module-customer": "100.0.*",
        "magento/module-payment": "100.0.*",
        "magento/module-quote": "100.0.*",
        "magento/module-backend": "100.0.*",
        "magento/module-directory": "100.0.*",
        "magento/module-theme": "100.0.*",
        "magento/framework": "100.0.*"

    },
    "type": "magento2-module",
    "version": "2.0.0",
    "license": [
        "proprietary"
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Webkul\\TestApi\\": ""
        }
    }
}

Module XML file

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Webkul_TestApi" setup_version="2.0.0" active="true"></module>
</config>

Now create TestApiManagementInterface.php file in app/code/Webkul/TestApi/Api/ folder:

<?php

namespace Webkul\TestApi\Api;

interface TestApiManagementInterface
{
    /**
     * get test Api data.
     *
     * @api
     *
     * @param int $id
     *
     * @return \Webkul\TestApi\Api\Data\TestApiInterface
     */
    public function getApiData($id);
}

the above will define all the api methods you want to expose, all these methods must have doc-block defined with @api, @params and @return else it will not work.

Now create TestApiManagementInterface implementation file in app/code/Webkul/TestApi/Model/Api folder:

<?php

namespace Webkul\TestApi\Model\Api;

class TestApiManagement implements \Webkul\TestApi\Api\TestApiManagementInterface
{
    const SEVERE_ERROR = 0;
    const SUCCESS = 1;
    const LOCAL_ERROR = 2;

    protected $_testApiFactory;

    public function __construct(
        \Webkul\TestApi\Model\TestApiFactory $testApiFactory

    ) {
        $this->_testApiFactory = $testApiFactory;
    }

    /**
     * get test Api data.
     *
     * @api
     *
     * @param int $id
     *
     * @return \Webkul\TestApi\Api\Data\TestApiInterface
     */
    public function getApiData($id)
    {
        try {
            $model = $this->_testApiFactory
                ->create();

            if (!$model->getId()) {
                throw new \Magento\Framework\Exception\LocalizedException(
                    __('no data found')
                );
            }

            return $model;
        } catch (\Magento\Framework\Exception\LocalizedException $e) {
            $returnArray['error'] = $e->getMessage();
            $returnArray['status'] = 0;
            $this->getJsonResponse(
                $returnArray
            );
        } catch (\Exception $e) {
            $this->createLog($e);
            $returnArray['error'] = __('unable to process request');
            $returnArray['status'] = 2;
            $this->getJsonResponse(
                $returnArray
            );
        }
    }
}

the above class is the implementation of the interface as you can see I have only created one method getApiData as it is defined in the interface its return type is \Webkul\TestApi\Api\Data\TestApiInterface class so now we have to create this class and its implementation too .

Now create a model TestApi.php inside app/code/Webkul/TestApi/Model, this is a fake model it is not connected to any table since its is only for testing purpose, I have just defined some setters and getters for some fields.

<?php

namespace Webkul\TestApi\Model;

/**
 * Marketplace Product Model.
 *
 * @method \Webkul\Marketplace\Model\ResourceModel\Product _getResource()
 * @method \Webkul\Marketplace\Model\ResourceModel\Product getResource()
 */
class TestApi  implements \Webkul\TestApi\Api\Data\TestApiInterface
{
    /**
     * Get ID.
     *
     * @return int
     */
    public function getId()
    {
        return 10;
    }

    /**
     * Set ID.
     *
     * @param int $id
     *
     * @return \Webkul\Marketplace\Api\Data\ProductInterface
     */
    public function setId($id)
    {
    }

    /**
     * Get title.
     *
     * @return string|null
     */
    public function getTitle()
    {
        return 'this is test title';
    }

    /**
     * Set title.
     *
     * @param string $title
     *
     * @return \Webkul\Marketplace\Api\Data\ProductInterface
     */
    public function setTitle($title)
    {
    }

    /**
     * Get desc.
     *
     * @return string|null
     */
    public function getDescription()
    {
        return 'this is test api description';
    }

    /**
     * Set Desc.
     *
     * @param string $desc
     *
     * @return \Webkul\Marketplace\Api\Data\ProductInterface
     */
    public function setDescription($desc)
    {
    }
}

the above class has getTitle, getDescription and getId so we must expect that API will return these values in the response.

Now create the interface class for the above implementation in app/code/Webkul/TestApi/Api/Data.

<?php
/**
 * Webkul Software.
 *
 * @category  Webkul
 *
 * @author    Webkul
 * @copyright Copyright (c) 2010-2016 Webkul Software Private Limited (https://webkul.com)
 * @license   https://store.webkul.com/license.html
 */

namespace Webkul\TestApi\Api\Data;

/**
 * Marketplace product interface.
 *
 * @api
 */
interface TestApiInterface
{
    /**#@+
     * Constants for keys of data array. Identical to the name of the getter in snake case
     */
    const ENTITY_ID = 'entity_id';

    const TITLE = 'title';

    const DESC = 'description';
    /**#@-*/

    /**
     * Get ID.
     *
     * @return int|null
     */
    public function getId();

    /**
     * Set ID.
     *
     * @param int $id
     *
     * @return \Webkul\Marketplace\Api\Data\ProductInterface
     */
    public function setId($id);

    /**
     * Get title.
     *
     * @return string|null
     */
    public function getTitle();

    /**
     * Set title.
     *
     * @param string $title
     *
     * @return \Webkul\Marketplace\Api\Data\ProductInterface
     */
    public function setTitle($title);

    /**
     * Get desc.
     *
     * @return string|null
     */
    public function getDescription();

    /**
     * Set Desc.
     *
     * @param string $desc
     *
     * @return \Webkul\Marketplace\Api\Data\ProductInterface
     */
    public function setDescription($desc);
}

Now create a webapi.xml file inside app/code/Webkul/TestApi/etc folder:

<?xml version="1.0"?>

<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <!-- test api Group -->
    <route url="/V1/testapi/custom/me" method="GET">
        <service class="Webkul\TestApi\Api\TestApiManagementInterface" method="getApiData"/>
        <resources>
            <resource ref="self"/>
        </resources>
        <data>
            <parameter name="id" force="true">%customer_id%</parameter>
        </data>
    </route>
</routes>

the above xml files defines the routes and their permissions, the route tag attributes:

attribute url defines the route for the web service attribute method defines the request type GET,PUT,POST or DELETE Now the service tag attributes:

class attribute is the interface class that defines the api methods. service attribute defines the exposed method now the resource tag defines the access control these can be three level of access:

Admin : for admin level access you need to define admin resource in the resource tag. Customer: for customer level access you need to set self in the resource. Guest: for guest level resources you need to define anonymous in the resource tag. I have defined self so this resource will work for customer level access.

This is the php file that you can create in your project to access the api resource

<?php

session_start();
/*
 *  base url of the magento host
 */
$host = 'http://magentohost';

//unset($_SESSION['access_token']);
if (!isset($_SESSION['access_token'])) {
    echo 'Authenticating...<br>';
     /*
     * authentication details of the customer
     */
    $username = '[email protected]';
    $password = 'Admin123';
    $postData['username'] = $username;
    $postData['password'] = $password;

    /*
     * init curl
     */
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $host.'rest/V1/integration/customer/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    /*
     * set content type and length
     */
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: '.strlen(json_encode($postData)),
        )
    );
    /*
     * setpost data
     */
    curl_setopt($ch, CURLOPT_POST, count($postData));
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
    $output = curl_exec($ch);
    curl_close($ch);
    /*
     * access token in json format
     */
    echo $output;
    $_SESSION['access_token'] = $output;
}
    if (isset($_SESSION['access_token'])) {
        /*
        * create headers for authorization
        */
        $headers = array(
            'Authorization: Bearer '.json_decode($_SESSION['access_token']),
        );
        echo '<pre>';
        echo 'api call... with key: '.$_SESSION['access_token'].'<br><br><br>';
        $ch = curl_init();
        /*
        * set api resource url
        */
        curl_setopt($ch, CURLOPT_URL, $host.'rest/V1/testapi/custom/me');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers
    );
        $output = curl_exec($ch);
        curl_close($ch);
        echo '<br>';
        /*
         * json response need to rtrim with [], some times it is appended to the respose so the json becomes invalid so need to rtrim the response
        */
        $test = json_decode(rtrim($output, '[]'));
        echo '
        =========================RESPONSE================================<br>
        ';

        print_r($test);
    }
exit(0);

in the above file I have used token based authentication. Magento2 provides 3 ways to access api resources :

Token based authentication 2: OAUTH based authentication 3: Session Based Authentication

You can learn more about it on magento2 api docs they have defined it very well:

http://devdocs.magento.com/guides/v2.0/get-started/authentication/gs-authentication-token.html

This is the response, taken snap from postman, you can see the below response is returned all the getters from the TestApi model in json format thats the beauty of magento2 api .

Source - https://webkul.com/blog/magento2-custom-rest-api/

enter image description here

2
  • I follow all the steps but where I put the id? I use Postman. Commented Sep 4, 2017 at 7:32
  • API output { "message": "%fieldName is a required field.", "parameters": { "fieldName": "id" } } Commented Sep 4, 2017 at 7:36
3

I think there are two options.

  1. Navigate to Stores> Configuration > Services > Magento Web API. Then select Yes from the Allow Anonymous Guest Access menu. I have tested, it works.
  2. Use OAuth-based authentication. But the official document seems incorrect. After integration registration, I active it and got consumer key, consumer secret, access token and access token secret. Then I can access the web APIs using OAuth1.0. Just like this. I use chrome extention Postman.enter image description here

There is no need for 2-lgeged Oauth Handshake. If I am wrong, please correct me. Thank you!

But if I use OAuth-based authentication in mobile app, I have to hardcode consumer key and so on inside app which can be easily reversed, and get the sensitive information, anyone have the token can access the APIs.

Besides, if I reauthorize the integration, the access token will change, the mobile app can not access the APIs until upgrade.

I am wondering how to use OAuth-based authentication correctly. Any help would be appreciated.

2
2

You can search for products instead of getting them. However there are indeed some things that were not included in the API or were not thought for mobile apps. For instance, if you need to create a cart using a call, that is expensive in terms of time and you always get the same cart number. Here you can find a happy path to make an order: https://stackoverflow.com/questions/33770986/how-to-place-an-order-using-magento2-api

In other different cases, like when you want to get information only accesible for admins, you can create extension_attributes to get further data.

1

yes Magento2 provide most of the APIs required to develop mobile app. There are a couple of APIs related to payments and my orders are missing but that can be developed easily. You can use token based authorization for the user authentication.

If you are looking for readily available solution then take a look at our app http://www.ipragmatech.com/products/android-magento-mobile-app/. Feel free to contact us if you have any question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.