Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\LoginAsCustomerApi\Api;


/**
* Interface defining the contract for generating access tokens used by the LoginAsCustomer functionality.
*
* @api
*/
interface GenerateLoginCustomerTokenInterface
{
/**
* Create access token with secret given the customer credentials.
*
* @param string $secret Temporary secret issued for Login As Customer authorization.
* @return string Generated access token
* @throws \Magento\Framework\Exception\AuthenticationException
*/
public function createCustomerAccessToken(string $secret): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\LoginAsCustomerApi\Model;

use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Integration\Api\TokenManager;
use Magento\Integration\Model\CustomUserContext;
use Magento\LoginAsCustomerApi\Api\ConfigInterface as LoginAsCustomerConfig;
use Magento\LoginAsCustomerApi\Api\GetAuthenticationDataBySecretInterface;
use Magento\LoginAsCustomerApi\Api\GenerateLoginCustomerTokenInterface;

/**
* @inheritdoc
*/
class GenerateLoginCustomerToken implements GenerateLoginCustomerTokenInterface
{
public function __construct(
private readonly GetAuthenticationDataBySecretInterface $getAuthenticationDataBySecret,
private readonly LoginAsCustomerConfig $loginAsCustomerConfig,
private readonly TokenManager $tokenManager,
) {
}

/**
* {@inheritdoc}
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function createCustomerAccessToken(string $secret): string
{
if (!$this->loginAsCustomerConfig->isEnabled()) {
throw new LocalizedException(__('Login As Customer module is disabled.'));
}
try {
$authenticationData = $this->getAuthenticationDataBySecret->execute($secret);
$customerId = $authenticationData->getCustomerId();
} catch (\Exception $e) {
throw new AuthenticationException(__('Invalid or expired secret.'));
}
$context = new CustomUserContext(
(int)$customerId,
CustomUserContext::USER_TYPE_CUSTOMER
);
$params = $this->tokenManager->createUserTokenParameters();
return $this->tokenManager->create($context, $params);
}
}
3 changes: 3 additions & 0 deletions app/code/Magento/LoginAsCustomerApi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ This module provides API for ability to login into customer account for an admin
- `\Magento\LoginAsCustomerApi\Api\SetLoggedAsCustomerCustomerIdInterface`:
- set id of customer admin is logged as

- `\Magento\LoginAsCustomerApi\Api\GenerateLoginCustomerTokenInterface`:
- generate an integration access token from a valid Login As Customer secret

For information about a public API, see [Public interfaces & APIs](https://developer.adobe.com/commerce/php/development/components/api-concepts/).

## Additional information
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);

namespace Magento\LoginAsCustomerApi\Test\Unit\Model;

use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Integration\Api\TokenManager;
use Magento\Integration\Model\CustomUserContext;
use Magento\Integration\Model\UserToken\UserTokenParameters;
use Magento\LoginAsCustomerApi\Api\ConfigInterface as LoginAsCustomerConfig;
use Magento\LoginAsCustomerApi\Api\GetAuthenticationDataBySecretInterface;
use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface;
use Magento\LoginAsCustomerApi\Model\GenerateLoginCustomerToken;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

/**
* Unit test for \Magento\LoginAsCustomerApi\Model\LoginAsCustomerTokenService
*/
class GenerateLoginCustomerTokenTest extends TestCase
{
/** @var GetAuthenticationDataBySecretInterface|MockObject */
private $getAuthenticationDataBySecret;

/** @var LoginAsCustomerConfig|MockObject */
private $loginAsCustomerConfig;

/** @var TokenManager|MockObject */
private $tokenManager;

/** @var GenerateLoginCustomerToken */
private $service;

protected function setUp(): void
{
$this->getAuthenticationDataBySecret = $this->createMock(GetAuthenticationDataBySecretInterface::class);
$this->loginAsCustomerConfig = $this->createMock(LoginAsCustomerConfig::class);
$this->tokenManager = $this->createMock(TokenManager::class);

$this->service = new GenerateLoginCustomerToken(
$this->getAuthenticationDataBySecret,
$this->loginAsCustomerConfig,
$this->tokenManager
);
}

public function testModuleDisabledThrowsException(): void
{
$this->loginAsCustomerConfig
->expects($this->once())
->method('isEnabled')
->willReturn(false);

$this->expectException(LocalizedException::class);
$this->expectExceptionMessage('Login As Customer module is disabled.');

$this->service->createCustomerAccessToken('secret123');
}

public function testInvalidSecretThrowsAuthenticationException(): void
{
$this->loginAsCustomerConfig
->expects($this->once())
->method('isEnabled')
->willReturn(true);

$this->getAuthenticationDataBySecret
->expects($this->once())
->method('execute')
->willThrowException(new \Exception('Secret invalid.'));

$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('Invalid or expired secret.');

$this->service->createCustomerAccessToken('invalid-secret');
}

public function testValidSecretReturnsToken(): void
{
$secret = 'valid-secret';
$customerId = 42;
$expectedToken = 'generated-token-xyz';

$this->loginAsCustomerConfig
->expects($this->once())
->method('isEnabled')
->willReturn(true);

$authenticationData = $this->createMock(AuthenticationDataInterface::class);
$authenticationData
->expects($this->once())
->method('getCustomerId')
->willReturn($customerId);

$this->getAuthenticationDataBySecret
->expects($this->once())
->method('execute')
->with($secret)
->willReturn($authenticationData);

$tokenParams = $this->createMock(UserTokenParameters::class);
$this->tokenManager
->expects($this->once())
->method('createUserTokenParameters')
->willReturn($tokenParams);

$this->tokenManager
->expects($this->once())
->method('create')
->with(
$this->callback(function ($context) use ($customerId) {
return $context instanceof CustomUserContext
&& $context->getUserId() === $customerId
&& $context->getUserType() === CustomUserContext::USER_TYPE_CUSTOMER;
}),
$tokenParams
)
->willReturn($expectedToken);

$result = $this->service->createCustomerAccessToken($secret);

$this->assertSame($expectedToken, $result);
}
}
21 changes: 21 additions & 0 deletions app/code/Magento/LoginAsCustomerApi/etc/acl.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!--
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
-->
<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="Magento_Customer::customer">
<resource id="Magento_LoginAsCustomerApi::token"
title="Generate Login As Customer Token"
sortOrder="60"/>
</resource>
</resource>
</resources>
</acl>
</config>
2 changes: 2 additions & 0 deletions app/code/Magento/LoginAsCustomerApi/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\LoginAsCustomerApi\Api\IsLoginAsCustomerEnabledForCustomerInterface"
type="Magento\LoginAsCustomerApi\Model\IsLoginAsCustomerEnabledForCustomerChain"/>
<preference for="Magento\LoginAsCustomerApi\Api\GenerateLoginCustomerTokenInterface"
type="Magento\LoginAsCustomerApi\Model\GenerateLoginCustomerToken"/>
</config>
17 changes: 17 additions & 0 deletions app/code/Magento/LoginAsCustomerApi/etc/webapi.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<!--
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
-->
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
<route url="/V1/integration/customer/login-as-customer" method="POST">
<service class="Magento\LoginAsCustomerApi\Api\GenerateLoginCustomerTokenInterface"
method="createCustomerAccessToken"/>
<resources>
<resource ref="Magento_LoginAsCustomerApi::token"/>
</resources>
</route>
</routes>
Loading