1

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.

0

1 Answer 1

1

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')
            ],
        );
    }
Sign up to request clarification or add additional context in comments.

2 Comments

Hi, I don't think I follow you completely. Yes, all of my application will be using the Client service. But where should I instantiate this service if not in the register method? And where should the singleton be created? Could you perhaps update your answer with a small code example?
@pu4cu sorry for confusing you, edited my response.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.