Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

pirmax's avatar

Make auth with custom user provider from remote API on Laravel 10

I'm desperate to use an API as a way to connect users on a Laravel 10 project.

I developed a custom user provider that fetches a token via the remote API (a service that I use and which does not belong to me). This API returns an email, a handle, and an access token and a refresh token.

When I connect via my form, I get a temporary session with the token, but when the page is reloaded, the "retrieveById" method is called and I try to have the token called the "me" method of the form. API to reconstruct a complete User::class object.

I can use sessions but I don't find it clean. I was just wondering why the "retrieveByCredentials" method doesn't return the User::class class as a whole with the data I pass to it...

My User::class :

<?php

namespace App\Models;

use Illuminate\Auth\GenericUser;

class User extends GenericUser
{
    //
}

My CustomUserProvider::class :

<?php

namespace App\Providers;

use App\Models\User;
use App\Services\Api\PdsService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;

class CustomUserProvider implements UserProvider
{
    public function retrieveById($identifier)
    {
        // fetch access token to use with API
        $result = PdsService::factory()->me();

        return new User([
            'id' => $identifier,
            'email' => $result['email'],
            'handle' => $result['handle'],
            'accessToken' => $result['accessJwt'],
            'refreshToken' => $result['refreshJwt'],
        ]);
    }

    public function retrieveByToken($identifier, $token)
    {
        //
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
        //
    }

    /**
     * @param array $credentials
     * @return User|null
     * @throws \Exception
     */
    public function retrieveByCredentials(array $credentials): ?User
    {
        $identifier = $credentials['identifier'] ?? null;
        $password = $credentials['password'] ?? null;

        if (is_null($identifier) || is_null($password)) {
            return null;
        }

        $result = PdsService::factory()
            ->login($identifier, $password);

        if (!empty($result['error'])) {
            return null;
        }

        return new User([
            'id' => $result['did'],
            'email' => $result['email'],
            'handle' => $result['handle'],
            'accessToken' => $result['accessJwt'],
            'refreshToken' => $result['refreshJwt'],
        ]);
    }

    public function validateCredentials(Authenticatable $user, array $credentials): bool
    {
        return true;
    }
}

In boot method in AuthServiceProvider::class :

Auth::provider('custom_provider', fn() => new CustomUserProvider);

And the auth config file :

'providers' => [
    'users' => [
        'driver' => 'custom_provider',
        'model' => App\Models\User::class,
    ],
],

Thanks.

0 likes
3 replies
martinbean's avatar

This API returns an email, a handle, and an access token and a refresh token.

@pirmax This suggests the API uses OAuth. You usually use the returned OAuth token to then get further details about the token owner (user) via a profile or “me” endpoint. You should then use this data to connect that user to a user in your database, and start a session in your application for that user.

Given you seem to get a “did” as an identifier for the user on the external service, you can use that to map external users to your users.

With Socialite, the flow would look something like this:

public function handleProviderCallback()
{
    $externalUser = Socialite::driver('[service]')->user();

    // Look up user by external ID
    $user = User::query()->where('external_id', '=', $user->getId())->first();

    // If a user with that external ID wasn't found, show an error
    if ($user === null) {
         // TODO: Show error and prompt user to register instead
    }

    // If a user was found in your database, authenticate them
    Auth::login($user);

    // TODO: Redirect to whatever URL now that user has been authenticated
}

You now don’t need to create a custom user provider that’s going to be executing API requests on every HTTP request to your application, and increase your application’s response times.

pirmax's avatar

In fact, my goal is not to have a user on the local database side, but rather to play with a remote user. The model is only used to exploit the data of this remote user.

This is in fact a connection to a PDS instance of Bluesky (ATProto). And it doesn't seem to me that there is an OAuth connection.

Thanks.

martinbean's avatar

@pirmax It’s using OAuth-like terminology (access token, refresh token) which means you can just treat it like any other OAuth provider such as Facebook, LinkedIn, GitHub, Twitter, etc.

The “did” is the unique identifier for a user, and the access token is a bearer token you can use to make authorised requests as that user to some API.

1 like

Please or to participate in this conversation.