Reading list Switch to dark mode

    How to secure Magento 2 API

    Updated 11 March 2024

    In this article, We will learn about how to secure Magento 2 API.

    We will generate tokens to access Magento 2 API. Magento 2 provides three types of API Authentication.

    •  Token based authentication
    • OAUTH based authentication
    • Session Based Authentication

    Also, Magento 2 Security extension helps secure your website form various attacks and hacks.

    1). Token based authentication

    To make a web API call from a client such as a Magento 2 mobile application, you must supply an access token.

    Request for Token

    Magento 2 provides a separate token service for administrators and customers. When you request a token from one of these services, the service returns a unique access token in exchange for an account’s username and password.

    Searching for an experienced
    Magento 2 Company ?
    Find out More

    Request for Customer Token

    Customer token : /V1/integration/customer/token

    customerToken

    Request for Admin Token

    Admin Token : /V1/integration/admin/token

    adminToken

    Authentication

    Magento 2 allow developers to define web API resources and their permissions in the webapi.xml configuration file.

    Before you can make web API calls, you must authenticate your identity and have the necessary permissions (authorization) to access the API resource. Authentication allows the application to identify the caller’s user type. A user’s (administrator, integration, customer, or guest) access rights determine an API call’s resource accessibility.

    USER TYPEACCESSIBLE RESOURCES (DEFINED IN WEBAPI.XML)
    Administrator or Integration if administrators are authorized for the Magento_Customer::group resource, they can make a GET /V1/customerGroups/:id call.
    CustomerResources with anonymous or self permission
    Guest userResources with anonymous permission

    Example for self

    The user authenticates him/herself by username & password then token will be generated in response that token act as self permission for further processes.

    <route url="/V1/customers" method="GET">
        <service class="Vendor\Module\Api\CustomerManagementInterface" method="getCustomerList"/>
        <resources>
            <resource ref="self"/>
        </resources>
        <data>
            <parameter name="customer_id" force="true">%customer_id%</parameter>
        </data>
    </route>

    Example for anonymous

    Web APIs to be accessed by unauthenticated users.

    <route url="/V1/products" method="GET"><br>    <service class="Venodor\Module\Api\ProductRepositoryInterface" method="getProducts"/><br>    <resources><br>        <resource ref="anonymous"/><br>    </resources><br></route>

    Example for admin authorized REST API

    Custom web API in Magento 2

    <route url="/V1/products" method="GET">
        <service class="Venodor\Module\Api\ProductRepositoryInterface" method="getProducts"/>
        <resources>
            <resource ref="Vendor_Module::name"/>
        </resources>
    </route>

    Create custom module ACl in etc/acl.xml

    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
        <acl>
            <resources>
                <resource id="Magento_Backend::admin">
                    <resource id="Vendor_Module::name" title="Pruduct List"/>
                </resource>
            </resources>
        </acl>
    </config>

    2). OAuth-based authentication

    OAuth authentication with Adobe Commerce and Magento Open Source is based on OAuth 1.0a, an open standard for secure API authentication. OAuth is a token-passing mechanism that allows a system to control which third-party applications have access to internal data without revealing or storing any user IDs or passwords.

    In Commerce, a third-party application that uses OAuth for authentication is called integration. An integration defines which resources the application can access. The application can be granted access to all resources or a customized subset of resources.

    As the process of registering the integration proceeds, Commerce creates the tokens that the application needs for authentication. It first creates a request token. This token is short-lived and must be exchanged for an access token. Access tokens are long-lived and will not expire unless the merchant revokes access from the application.

    OAuth authentication process

    The following diagram shows the OAuth authentication process. Each step is described further.

    aPIoyhtg

    1). Create an integration:- The merchant creates an integration from Admin. Commerce generates a consumer key and a consumer secret.

    2). Activate the integration:- The OAuth process begins when the merchant activates the integration. Magento sends the OAuth consumer key and secret, an OAuth verifier, and the store URL to the external application via HTTPS post to the page defined in the Callback Link field in Admin. See Activate an integration for more information.

    3). Process activation information:- The integrator must store the activation information received in step 2. These parameters will be used to ask for tokens.

    4). Call the application’s login page:- Commerce calls the page defined in the Identity Link field in Admin.

    5). Merchant logs in to the external application:- If the login is successful, the application returns to the location specified in the call. The login page is dismissed.

    6). Ask for a request token:- The application uses the POST /oauth/token/request REST API to ask for a request token. The Authorization header includes the consumer key and other information. See Get a request token for details about this token request.

    7). Send the request token:- Commerce returns a request token and request token secret.

    8). Ask for an access token:- The application uses the POST /oauth/token/access REST API to ask for an access token. The Authorization header includes the request token and other information. See Get an access token for details about this token request.

    9). Commerce sends the access token:- If this request is successful, Magento returns an access token and access token secret.

    10). The application can access Magento resources:- All requests sent to Commerce must use the full set of request parameters in Authorization header. See Access the web APIs for more information.

    Activate an integration

    The integration must be configured from the Admin (System > Extensions > Integrations). The configuration includes a callback URL and an identity link URL. The callback URL specifies where OAuth credentials can be sent when using OAuth for token exchange. The identity link points to the login page of the third-party application that is integrating with Commerce.

    save

    A merchant can choose to select Save and Activate when the integration is created. Alternatively, the merchant can click on Activate against a previously saved integration from the Integration grid.

    When the integration is created, Commerce generates a consumer key and a consumer secret.

    Activating the integration submits the credentials to the endpoint specified when creating the Integration. An HTTP POST from Commerce to the Integration endpoint will contain these attributes:

    • store_base_url
    • oauth_verifier
    • oauth_consumer_key
    • oauth_consumer_secret

    Integrations use the oauth_consumer_key key to get a request token and the oauth_verifier to get an access token.

    OAuth handshake details

    The process of completing the OAuth handshake requires that you,

    • Get a request token
    • Get an access token

    Get a request token

    A request token is a temporary token that the user exchanges for an access token. Use the following API to get a request token from Commerce:

    POST /oauth/token/request

    You must include these request parameters in the Authorization header in the call:

    PARAMETERDESCRIPTION
    oauth_consumer_keyThe consumer key is generated when you create the integration.
    oauth_signature_methodThe name of the signature method used to sign the request. Must be the value HMAC-SHA256.
    oauth_signatureA generated value (signature)
    oauth_nonceA random value that is uniquely generated by the application.
    oauth_timestampA positive integer, expressed in the number of seconds since January 1, 1970 00:00:00 GMT.
    oauth_versionThe OAuth version.

    The response contains these fields:

    1). oauth_token:- The token to be used when requesting an access token.

    2). oauth_token_secret:- A secret value that establishes ownership of the token.

    The response looks like this:

    oauth_token=4cqw0r7vo0s5goyyqnjb72sqj3vxwr0h&oauth_token_secret=rig3x3j5a9z5j6d4ubjwyf9f1l21itrr

    Get an access token

    The request token must be exchanged for an access token. Use the following API to get an access token from Commerce:

    POST /oauth/token/access

    You must include these request parameters in the Authorization header in the call:

    This process is known as a 2-legged OAuth handshake.

    PARAMETERDESCRIPTION
    oauth_consumer_keyThe consumer key value that you retrieve after you register the integration.
    oauth_nonceA random value that is uniquely generated by the application.
    oauth_signatureA generated value (signature)
    oauth_signature_methodThe name of the signature method used to sign the request. Must be the value HMAC-SHA256.
    oauth_timestampA positive integer, expressed in the number of seconds since January 1, 1970 00:00:00 GMT.
    oauth_versionThe OAuth version.
    oauth_tokenThe oauth_token value, or request token, obtained in Get a request token.
    oauth_verifierThe verification code that is tied to the consumer and request token. It is sent as part of the initial POST operation when the integration is activated.

    The response looks like this:

    oauth_token=0lnuajnuzeei2o8xcddii5us77xnb6v0&oauth_token_secret=1c6d2hycnir5ygf39fycs6zhtaagx8pd

    Access the web APIs

    After the integration is authorized to make API calls, third-party applications (registered as integrations) can invoke web APIs by using the access token.

    To use the access token to make web API calls:

    GET /rest/V1/products/1

    You must include these request parameters in the Authorization request header in the call:

    • oauth_consumer_key
    • oauth_nonce
    • oauth_signature_method
    • oauth_signature
    • oauth_timestamp
    • oauth_token

    The OAuth signature

    All OAuth handshake requests and Web Api requests include the signature as part of Authorization header. Its generated as follows:

    You concatenate a set of URL-encoded attributes and parameters to construct the signature base string.

    • HTTP method
    • URL
    • oauth_nonce
    • oauth_signature_method
    • oauth_timestamp
    • oauth_version
    • oauth_consumer_key
    • oauth_token

     You must use the HMAC-SHA256 signature method. The signing key is the concatenated values of the consumer secret and token secret separated by the ampersand character (ASCII code 38), even if empty. You must use parameter encoding to encode each value.

    OAuth token exchange example

    The scripts provided in this document simulate the Commerce OAuth 1.0a token exchange flow. You can drop these scripts under the document root directory of your installation so that they can be exposed as endpoints that your system can interact with to mimic the token exchange.

    OAuth 1.0a token exchange step:

    1). Login to your Admin and navigate to System > Extensions > Integrations

    2). Click on Add New Integration.

    3). Complete all details in the Integration Info tab

    • Name : SomeUniqueIntegrationName
    • Callback URL : http://your_app_host/endpoint.php
    • Identity link URL : http://your_app_host/login.php
    • Add permissions as desired on the API tab

    4). Select the Save and Activate option from the drop down menu.

    5). A pop-up window displays, confirming API permissions. Click Allow. (Make sure your browser allows pop-up windows.) The credentials are posted to endpoint.php. You should also see another pop-up for the identity linking step that opens the script from login.php.

    6). Click Login. (There is no actual login check since this is a simulation.). The checklogin.php script is called. It uses the posted credentials to complete the token exchange.

    7). When the token exchange completes successfully, the user is redirected back to the Integrations grid. The newly-created integration should be in the Active state.

    8). Click on the edit icon of the integration and check the Integration Details on the Integration Info tab. It should show all the credentials that can be used to make an authenticated API request using OAuth 1.0.

    checklogin.php

    <?php
    require './vendor/autoload.php';
    
    $consumerKey = $_REQUEST['oauth_consumer_key'];
    $callback = $_REQUEST['callback_url'];
    
    session_id('test');
    session_start();
    
    /** Use $consumerKey to retrieve the following data in case it was stored in DB when received at "endpoint.php" */
    if ($consumerKey !== $_SESSION['oauth_consumer_key']) {
        throw new \Exception("Consumer keys received on different requests do not match.");
    }
    
    $consumerSecret = $_SESSION['oauth_consumer_secret'];
    $magentoBaseUrl = rtrim($_SESSION['store_base_url'], '/');
    $oauthVerifier = $_SESSION['oauth_verifier'];
    
    define('TESTS_BASE_URL', $magentoBaseUrl);
    
    $credentials = new \OAuth\Common\Consumer\Credentials($consumerKey, $consumerSecret, $magentoBaseUrl);
    $oAuthClient = new OauthClient($credentials);
    $requestToken = $oAuthClient->requestRequestToken();
    $accessToken = $oAuthClient->requestAccessToken(
        $requestToken->getRequestToken(),
        $oauthVerifier,
        $requestToken->getRequestTokenSecret()
    );
    header("location: $callback");

    endpoint.php

    <?php
    session_id('test');
    session_start();
    
    // If this data is stored in the DB, oauth_consumer_key can be used as ID to retrieve this data later in "checklogin.php"
    // For simplicity of this sample, it is stored in session
    $_SESSION['oauth_consumer_key'] = $_POST['oauth_consumer_key'];
    
    $_SESSION['oauth_consumer_secret'] = $_POST['oauth_consumer_secret'];
    $_SESSION['store_base_url'] = $_POST['store_base_url'];
    $_SESSION['oauth_verifier'] = $_POST['oauth_verifier'];
    
    session_write_close();
    
    header("HTTP/1.0 200 OK");
    echo "Response";

    login.php

    <?php
    $consumerKey = $_REQUEST['oauth_consumer_key'];
    $callbackUrl = urlencode(urldecode($_REQUEST['success_call_back']));
    
    echo <<<HTML
    <table width="300" border="0" align="center" cellpadding="0" cellspacing="1" bgcolor="#CCCCCC">
        <tr>
            <form name="form1" method="post" action="checklogin.php?oauth_consumer_key={$consumerKey}&callback_url={$callbackUrl}">
                <td>
                    <table width="100%" border="0" cellpadding="3" cellspacing="1" bgcolor="#FFFFFF">
                        <tr>
                            <td colspan="3"><strong>Integrations Login</strong></td>
                        </tr>
                        <tr>
                            <td width="78">Username</td>
                            <td width="6">:</td>
                            <td width="294"><input name="myusername" type="text" id="myusername"></td>
                        </tr>
                        <tr>
                            <td>Password</td>
                            <td>:</td>
                            <td><input name="mypassword" type="text" id="mypassword"></td>
                        </tr>
                        <tr>
                            <td>&nbsp;</td>
                            <td>&nbsp;</td>
                            <td><input type="submit" name="Submit" value="Login"></td>
                        </tr>
                    </table>
                </td>
            </form>
        </tr>
    </table>
    HTML;

    OauthClient.php

    <?php
    
    use OAuth\Common\Consumer\Credentials;
    use OAuth\Common\Http\Client\ClientInterface;
    use OAuth\Common\Http\Exception\TokenResponseException;
    use OAuth\Common\Http\Uri\Uri;
    use OAuth\Common\Http\Uri\UriInterface;
    use OAuth\Common\Storage\TokenStorageInterface;
    use OAuth\OAuth1\Service\AbstractService;
    use OAuth\OAuth1\Signature\SignatureInterface;
    use OAuth\OAuth1\Token\StdOAuth1Token;
    use OAuth\OAuth1\Token\TokenInterface;
    
    class OauthClient extends AbstractService
    {
        /** @var string|null */
        protected $_oauthVerifier = null;
    
        public function __construct(
            Credentials $credentials,
            ClientInterface $httpClient = null,
            TokenStorageInterface $storage = null,
            SignatureInterface $signature = null,
            UriInterface $baseApiUri = null
        ) {
            if (!isset($httpClient)) {
                $httpClient = new \OAuth\Common\Http\Client\StreamClient();
            }
            if (!isset($storage)) {
                $storage = new \OAuth\Common\Storage\Session();
            }
            if (!isset($signature)) {
                $signature = new \OAuth\OAuth1\Signature\Signature($credentials);
            }
            parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
        }
    
        /**
         * @return UriInterface
         */
        public function getRequestTokenEndpoint()
        {
            return new Uri('http://my.host/oauth/token/request');
        }
    
        /**
         * Returns the authorization API endpoint.
         *
         * @throws \OAuth\Common\Exception\Exception
         */
        public function getAuthorizationEndpoint()
        {
            throw new \OAuth\Common\Exception\Exception(
                'The REST API is 2-legged. Current operation is not available.'
            );
        }
    
        /**
         * Returns the access token API endpoint.
         *
         * @return UriInterface
         */
        public function getAccessTokenEndpoint()
        {
            return new Uri('http://magento.host/oauth/token/access');
        }
    
        /**
         * Parses the access token response and returns a TokenInterface.
         *
         * @param string $responseBody
         * @return TokenInterface
         */
        protected function parseAccessTokenResponse($responseBody)
        {
            return $this->_parseToken($responseBody);
        }
    
        /**
         * Parses the request token response and returns a TokenInterface.
         *
         * @param string $responseBody
         * @return TokenInterface
         * @throws TokenResponseException
         */
        protected function parseRequestTokenResponse($responseBody)
        {
            $data = $this->_parseResponseBody($responseBody);
            if (isset($data['oauth_verifier'])) {
                $this->_oauthVerifier = $data['oauth_verifier'];
            }
            return $this->_parseToken($responseBody);
        }
    
        /**
         * Parse response body and create oAuth token object based on parameters provided.
         *
         * @param string $responseBody
         * @return StdOAuth1Token
         * @throws TokenResponseException
         */
        protected function _parseToken($responseBody)
        {
            $data = $this->_parseResponseBody($responseBody);
            $token = new StdOAuth1Token();
            $token->setRequestToken($data['oauth_token']);
            $token->setRequestTokenSecret($data['oauth_token_secret']);
            $token->setAccessToken($data['oauth_token']);
            $token->setAccessTokenSecret($data['oauth_token_secret']);
            $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
            unset($data['oauth_token'], $data['oauth_token_secret']);
            $token->setExtraParams($data);
            return $token;
        }
    
        /**
         * Parse response body and return data in array.
         *
         * @param string $responseBody
         * @return array
         * @throws \OAuth\Common\Http\Exception\TokenResponseException
         */
        protected function _parseResponseBody($responseBody)
        {
            if (!is_string($responseBody)) {
                throw new TokenResponseException("Response body is expected to be a string.");
            }
            parse_str($responseBody, $data);
            if (null === $data || !is_array($data)) {
                throw new TokenResponseException('Unable to parse response.');
            } elseif (isset($data['error'])) {
                throw new TokenResponseException("Error occurred: '{$data['error']}'");
            }
            return $data;
        }
    
        /**
         * @override to fix since parent implementation from lib not sending the oauth_verifier when requesting access token
         * Builds the authorization header for an authenticated API request
         *
         * @param string $method
         * @param UriInterface $uri the uri the request is headed
         * @param \OAuth\OAuth1\Token\TokenInterface $token
         * @param $bodyParams array
         * @return string
         */
        protected function buildAuthorizationHeaderForAPIRequest(
            $method,
            UriInterface $uri,
            TokenInterface $token,
            $bodyParams = null
        ) {
            $this->signature->setTokenSecret($token->getAccessTokenSecret());
            $parameters = $this->getBasicAuthorizationHeaderInfo();
            if (isset($parameters['oauth_callback'])) {
                unset($parameters['oauth_callback']);
            }
    
            $parameters = array_merge($parameters, ['oauth_token' => $token->getAccessToken()]);
            $parameters = array_merge($parameters, $bodyParams);
            $parameters['oauth_signature'] = $this->signature->getSignature($uri, $parameters, $method);
    
            $authorizationHeader = 'OAuth ';
            $delimiter = '';
    
            foreach ($parameters as $key => $value) {
                $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
                $delimiter = ', ';
            }
    
            return $authorizationHeader;
        }
    }

    3). Session-based authentication

    As a customer, you log in to the storefront with your customer credentials. As an admin, you log in to the Admin with your admin credentials.

    The web API framework uses your logged-in session information to verify your identity and authorize access to the requested resource.

    Customers can access resources that are configured with anonymous or self permission in the webapi.xml configuration file.

    Admins can access resources that are assigned to their Admin profile.

    For example, if a customer is logged in to the storefront and the JavaScript widget invokes the self API, details for the logged-in customer are fetched:

    GET /rest/V1/customers/me

    if an admin is logged in to the Admin and the JavaScript widget invokes the Magento_Customer::group API, details for the logged-in admin are fetched. The web API framework establishes the identity of the admin user based on logged-in session information and authorizes access to the Magento_Customer::group resource.

    Admin session-based authentication is not currently possible for API endpoints.

    The session based authentication functionality is restricted to AJAX calls. Direct browser requests cannot be made due to security vulnerabilities.

    Hope this will help you.

    Thanks 🙂

    . . .

    Leave a Comment

    Your email address will not be published. Required fields are marked*


    Be the first to comment.

    Back to Top

    Message Sent!

    If you have more details or questions, you can reply to the received confirmation email.

    Back to Home