So I'm working on a Laravel admin application, which consumes an external API, let's call it PlatformAPI. The way Platform works, is that the users of my application have an account on Platform. My Laravel application will function as a admin dashboard, so the users can view some basic reports, which are fetched from PlatformAPI.
Every user in my application has to add their client ID and client secret, which they can create in Platform. In this way, my application will be able to perform requests to PlatformAPI on their behalf, using the users' credentials.
I've read some articles and tutorials on basically setting up the credentials/tokens for a Service and bind that service to the ServiceProvider like so:
<?php
namespace App\Services\PlatformAPI;
class Client
{
protected string $clientId;
protected string $clientSecret;
public function __construct(string $clientId, string $clientSecret)
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
}
public function getSales(string $month)
{
// ...
}
}
<?php
use App\Services\PlatformApi\Client;
class PlatformApiServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(Client::class, function ($app) {
return new Client(
clientId: config('services.platform-api.client-id'),
clientSecret: config('services.platform-api.client-secret'),
);
});
}
}
This way, you wouldn't have to set the client credentials each time you want to consume PlatformApi and I could call the service like so:
<?php
namespace App\Http\Controllers;
use App\Services\PlatformApi\Client;
class RandomController extends Controller
{
protected Client $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function index()
{
$this->client->getSales(string $month);
}
}
However, since I need to perform requests to PlatformApi on behalf of my application's users, with the credentials that they have provided (and are stored in my application's database), I'm not sure whether this same approach would work, since a singleton only instances once?
Also, in order to consume PlatformApi, I need to get the access token, using the users' credentials. This access token will need to be stored somewhere as well (I'm thinking in the cache).
I'm kinda stuck on how to approach this. Any pointers would be much appreciated.
CodePudding user response:
I assume all of your application will be using this Client service. If so, you can keep using the singleton design pattern for it (to stop further oauth
requests), but try to separate the logic from the provider register
method. You can instanciate the Client class after calling a private method that returns a valid access_token
(checks the DB
/ Cache
if there's a valid token by the expires_in
timestamp value and returns it, or requests a new one with the user client
/secret
and returns it)
/**
* Register any application services.
*
* @return void
*/
public function register(): void
{
$this->app->singleton(Client::class, function () {
return new Client(
accessToken: $this->getUserToken()->access_token
);
});
}
/**
* Tries to get the user token from the database.
*
* @return ClientCredentials
*/
private function getUserToken(): ClientCredentials
{
$credentials = ClientCredentials::query()
->latest()
->find(id: auth()->user()->id);
if ($credentials === null || now() > $credentials->expires_at) {
$credentials = $this->requestUserToken();
}
return $credentials;
}
/**
* Requests a new token for the user & stores it in the database.
*
* @return ClientCredentials
*/
private function requestUserToken(): ClientCredentials
{
$tokenResponse = API::requestToken(
client: auth()->user()->client,
secret: auth()->user()->secret,
);
return ClientCredentials::query()->create(
attributes: [
'user_id' => auth()->user()->id,
'access_token' => $tokenResponse['access_token'],
'refresh_token' => $tokenResponse['refresh_token'],
'token_type' => 'Bearer',
'expires_at' => new DateTime(datetime: ' ' . $tokenResponse['expires_in'] . ' seconds')
],
);
}