Home  >  Q&A  >  body text

How to use Laravel's service provider for external API usage (leveraging user-based credentials)

So I'm developing a Laravel admin application that uses an external API, let's call it PlatformAPI. The way the platform works is that users of my app have an account on the platform. My Laravel application will act as an admin dashboard so users can view some basic reports obtained from the PlatformAPI.

Every user in my application must add their client ID and client secret, which they can create in the platform. This way, my application will be able to perform requests to the PlatformAPI on their behalf using the user's credentials.

I've read some articles and tutorials that basically describe how to set up credentials/tokens for a service and bind that service to a ServiceProvider like this:

<?php

namespace AppServicesPlatformAPI;

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 AppServicesPlatformApiClient;

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 don't have to set the client credentials every time you want to use the PlatformApi, I can just call the service like this:

<?php

namespace AppHttpControllers;

use AppServicesPlatformApiClient;

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 the PlatformApi on behalf of the user of my app using the credentials they provide (and store in my app's database), I'm not sure if this same approach will work because Can a singleton be instantiated only once?

Additionally, in order to use the PlatformApi, I need to obtain an access token using the user's credentials. This access token also needs to be stored somewhere (in cache I guess).

I'm a bit at a loss as to how to solve this problem. Any pointers would be greatly appreciated.

P粉465675962P粉465675962334 days ago445

reply all(1)I'll reply

  • P粉523625080

    P粉5236250802023-12-14 13:41:31

    I assume all your applications will use this client service. If so, you can continue to use the singleton design pattern (to stop further oauth requests), but try to separate the logic from the provider's register method. You can instantiate the Client class after calling a private method that returns a valid access_token (checking DB / Cache expires_in# if a valid token exists ## timestamp value and return it, or request a new client/secret from the user and return 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')
                ],
            );
        }

    reply
    0
  • Cancelreply