Authentication: tokens and integrations

How admin, customer and integration callers get credentials, how long each lasts, and what two-factor authentication changed.

SyntaxAuthorization: Bearer <access token>

A REST call is made by one of three kinds of caller, and each gets its credentials differently.

Admin and customer tokens

An access token is a string sent on every request in the header Authorization: Bearer <token>. A customer gets one by posting a username and password to /V1/integration/customer/token; an admin user posts the same shape to /V1/integration/admin/token. Both routes are anonymous, since the caller has no token yet. The response body is the token as a quoted JSON string. In 2.4.9 a customer token lasts 1 hour and an admin token 4 hours by default, both set under Stores > Settings > Configuration > Services > OAuth > Access Token Expiration. A cron job removes expired tokens hourly.

Integrations

An integration is a named third-party consumer created in System > Extensions > Integrations, where you tick the ACL resources it may use and press Activate. Magento then shows four values: consumer key, consumer secret, access token and access token secret. An integration's access token does not expire until you revoke it.

What trips people up

Two-factor authentication is bundled and enabled in 2.4, so /V1/integration/admin/token fails for an account that has a provider configured. Post to the provider route instead, for example /V1/tfa/provider/google/authenticate with username, password and otp. Since 2.4.4 an integration's access token is not accepted as a plain bearer token unless bin/magento config:set oauth/consumer/enable_integration_as_bearer 1 is set; otherwise the integration must sign each request with OAuth 1.0a. Apply configuration changes with bin/magento cache:flush.

Example

Example · bash
# 1. Customer token: valid 1 hour by default.
curl -s -X POST "https://example.com/rest/V1/integration/customer/token" \
  -H "Content-Type: application/json" \
  -d '{"username":"[email protected]","password":"CustomerPass1"}'

# "wcj3qflp9rm9s2rnmsrb6ygvgh0q1d2c"

# 2. Use it. /V1/customers/me resolves the id from the token, not from the URL.
curl -s "https://example.com/rest/V1/customers/me" \
  -H "Authorization: Bearer wcj3qflp9rm9s2rnmsrb6ygvgh0q1d2c"

# 200 OK, the authenticated customer's own record

# 3. Admin token with Google Authenticator configured (2FA ships enabled in 2.4).
curl -s -X POST "https://example.com/rest/V1/tfa/provider/google/authenticate" \
  -H "Content-Type: application/json" \
  -d '{"username":"api_user","password":"ApiPass123","otp":"123456"}'

# "6ovhgkgyu3n7i1pn0h2ucbj8fbrr5kqd"   (valid 4 hours by default)

# 4. Shorten the admin token lifetime to 1 hour, then apply it.
bin/magento config:set oauth/access_token_lifetime/admin 1
bin/magento cache:flush

When to use it

  • Script a nightly stock export that authenticates once and reuses the token for the run.
  • Let a storefront app sign a customer in and then read that customer's orders.
  • Hand a logistics provider credentials that cover shipments and nothing else.

More examples

Shipping an integration with your module

This pre-creates the integration so a merchant installing your module finds it already listed in System > Extensions > Integrations and only has to press Activate. The endpoint_url is where Magento posts the credentials when the integration is activated, and identity_link_url is where the merchant is sent to link their account. Add Magento_Integration to the sequence in your module.xml so it loads first, then run bin/magento setup:upgrade.

Example · xml
<!-- app/code/Vendor/Reviews/etc/integration/config.xml -->
<integrations>
    <integration name="VendorReviewsSync">
        <email>[email protected]</email>
        <endpoint_url>https://sync.example.com/magento/callback</endpoint_url>
        <identity_link_url>https://sync.example.com/magento/login</identity_link_url>
    </integration>
</integrations>

Declaring which resources the integration asks for

api.xml is the list of permissions the merchant is asked to approve, and it must name resources that exist in some module's acl.xml. Keep it to the routes the integration actually calls: these resources are what the framework checks on every request, and asking for more than you need is the difference between a cautious merchant approving the integration and refusing it. The names here are the same identifiers used by ref in webapi.xml.

Example · xml
<!-- app/code/Vendor/Reviews/etc/integration/api.xml -->
<integrations>
    <integration name="VendorReviewsSync">
        <resources>
            <resource name="Magento_Catalog::products"/>
            <resource name="Vendor_Reviews::reviews"/>
            <resource name="Vendor_Reviews::reviews_save"/>
        </resources>
    </integration>
</integrations>

Knowing which caller you are serving

The token type decides what your service may do, and this is how you read it: USER_TYPE_INTEGRATION is 1, USER_TYPE_ADMIN is 2, USER_TYPE_CUSTOMER is 3, USER_TYPE_GUEST is 4. Reach for it when you want an audit trail on writes, or when one endpoint serves both an admin tool and a storefront. The match expression is the shorthand form of a switch with a default branch, and returns a value instead of assigning one.

Example · php
<?php
// app/code/Vendor/Reviews/Model/ReviewAudit.php

declare(strict_types=1);

namespace Vendor\Reviews\Model;

use Magento\Authorization\Model\UserContextInterface;

class ReviewAudit
{
    public function __construct(
        private readonly UserContextInterface $userContext
    ) {
    }

    public function describeCaller(): string
    {
        return match ($this->userContext->getUserType()) {
            UserContextInterface::USER_TYPE_INTEGRATION => 'integration',
            UserContextInterface::USER_TYPE_ADMIN => 'admin:' . $this->userContext->getUserId(),
            UserContextInterface::USER_TYPE_CUSTOMER => 'customer:' . $this->userContext->getUserId(),
            default => 'guest',
        };
    }
}

// Output for a customer token belonging to customer 17:
// customer:17

Discussion

  • Be the first to comment on this lesson.