forked from apoloz/alfa-bank-sdk
Add H2H authentication service and new example flows
- Implement H2H authentication service for secure client authentication - Add new examples for authorization code flow and H2H auth service - Include example for generating client secret - Update core connector and token request functionality - Enhance README with new features Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -11,10 +11,36 @@ use Apoloz\AlfaBank\Core\AlfaConfig;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
use Apoloz\AlfaBank\Services\OpenSslCmsSigner;
|
||||
use Apoloz\AlfaBank\Sbp\SbpConnector;
|
||||
use Apoloz\AlfaBank\Services\H2HAuthService;
|
||||
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
|
||||
|
||||
class AlfaBank
|
||||
{
|
||||
/**
|
||||
* Creates a helper service for H2H Authorization (Authorization Code Flow).
|
||||
*/
|
||||
public static function auth(
|
||||
string $clientId,
|
||||
string $clientSecret,
|
||||
string $redirectUri,
|
||||
string $certificatePath,
|
||||
string $privateKeyPath,
|
||||
Environment $environment = Environment::SANDBOX
|
||||
): H2HAuthService {
|
||||
$config = new \Apoloz\AlfaBank\Core\AlfaConfig(
|
||||
baseUrl: $environment->value,
|
||||
certificatePath: $certificatePath,
|
||||
privateKeyPath: $privateKeyPath,
|
||||
clientId: $clientId,
|
||||
clientSecret: $clientSecret
|
||||
);
|
||||
|
||||
$signer = new \Apoloz\AlfaBank\Services\OpenSslCmsSigner($certificatePath, $privateKeyPath);
|
||||
$connector = new \Apoloz\AlfaBank\Core\AlfaBankConnector($config, $signer);
|
||||
|
||||
return new H2HAuthService($connector, $clientId, $redirectUri, $environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a health check on the connection.
|
||||
* Throws an exception if authentication or account listing fails.
|
||||
|
||||
@@ -79,6 +79,55 @@ class AlfaBankConnector extends Connector implements HasPagination
|
||||
return new AlfaOffsetPaginator($this, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange Authorization Code for tokens (H2H Flow).
|
||||
*/
|
||||
public function authWithCode(string $code, string $redirectUri): \Apoloz\AlfaBank\Data\Auth\TokenData
|
||||
{
|
||||
if (!$this->alfaConfig->clientId || !$this->alfaConfig->clientSecret) {
|
||||
throw new \RuntimeException("Client ID/Secret not configured.");
|
||||
}
|
||||
|
||||
$request = new GetTokenRequest(
|
||||
clientId: $this->alfaConfig->clientId,
|
||||
clientSecret: $this->alfaConfig->clientSecret,
|
||||
code: $code,
|
||||
redirectUri: $redirectUri,
|
||||
grantType: 'authorization_code'
|
||||
);
|
||||
|
||||
$response = $this->send($request);
|
||||
$data = $response->dto();
|
||||
|
||||
$this->authenticate(new \Saloon\Http\Auth\TokenAuthenticator($data->access_token));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token using refresh token.
|
||||
*/
|
||||
public function refreshToken(string $refreshToken): \Apoloz\AlfaBank\Data\Auth\TokenData
|
||||
{
|
||||
if (!$this->alfaConfig->clientId || !$this->alfaConfig->clientSecret) {
|
||||
throw new \RuntimeException("Client ID/Secret not configured.");
|
||||
}
|
||||
|
||||
$request = new GetTokenRequest(
|
||||
clientId: $this->alfaConfig->clientId,
|
||||
clientSecret: $this->alfaConfig->clientSecret,
|
||||
refreshToken: $refreshToken,
|
||||
grantType: 'refresh_token'
|
||||
);
|
||||
|
||||
$response = $this->send($request);
|
||||
$data = $response->dto();
|
||||
|
||||
$this->authenticate(new \Saloon\Http\Auth\TokenAuthenticator($data->access_token));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function login(?string $clientId = null, ?string $clientSecret = null): static
|
||||
{
|
||||
// Allow overriding config via method arguments
|
||||
|
||||
@@ -42,6 +42,7 @@ class GetTokenRequest extends Request implements HasBody
|
||||
protected string $clientSecret,
|
||||
protected ?string $code = null,
|
||||
protected ?string $redirectUri = null,
|
||||
protected ?string $refreshToken = null,
|
||||
protected string $grantType = 'authorization_code'
|
||||
) {}
|
||||
// </SECTION>
|
||||
@@ -64,11 +65,15 @@ class GetTokenRequest extends Request implements HasBody
|
||||
'client_secret' => $this->clientSecret,
|
||||
];
|
||||
|
||||
if($this->grantType === 'authorization_code') {
|
||||
if ($this->grantType === 'authorization_code') {
|
||||
$body['code'] = $this->code;
|
||||
$body['redirect_uri'] = $this->redirectUri;
|
||||
}
|
||||
|
||||
if ($this->grantType === 'refresh_token') {
|
||||
$body['refresh_token'] = $this->refreshToken;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
61
src/Services/H2HAuthService.php
Normal file
61
src/Services/H2HAuthService.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Apoloz\AlfaBank\Services;
|
||||
|
||||
use Apoloz\AlfaBank\Core\AlfaBankConnector;
|
||||
use Apoloz\AlfaBank\Data\Auth\TokenData;
|
||||
use Apoloz\AlfaBank\Enums\Environment;
|
||||
|
||||
class H2HAuthService
|
||||
{
|
||||
public function __construct(
|
||||
protected AlfaBankConnector $connector,
|
||||
protected string $clientId,
|
||||
protected string $redirectUri,
|
||||
protected Environment $environment = Environment::SANDBOX
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Step 1: Generate the URL for the user to authorize via Alfa ID.
|
||||
*
|
||||
* @param array<string> $scopes List of scopes (e.g. ['beneficiaries', 'payment'])
|
||||
* @param string|null $state Optional state parameter for CSRF protection
|
||||
*/
|
||||
public function getAuthUrl(array $scopes = ['beneficiaries'], ?string $state = null): string
|
||||
{
|
||||
$baseUrl = $this->environment === Environment::PRODUCTION
|
||||
? 'https://id.alfabank.ru/oidc/authorize'
|
||||
: 'https://id-sandbox.alfabank.ru/oidc/authorize';
|
||||
|
||||
$params = [
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->clientId,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'scope' => implode(' ', $scopes),
|
||||
];
|
||||
|
||||
if ($state) {
|
||||
$params['state'] = $state;
|
||||
}
|
||||
|
||||
return $baseUrl . '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Exchange the received code for Access & Refresh Tokens.
|
||||
*/
|
||||
public function exchangeCode(string $code): TokenData
|
||||
{
|
||||
return $this->connector->authWithCode($code, $this->redirectUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Refresh the token using a stored refresh token.
|
||||
*/
|
||||
public function refreshToken(string $refreshToken): TokenData
|
||||
{
|
||||
return $this->connector->refreshToken($refreshToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user