Skip to content

Instantly share code, notes, and snippets.

@EduardoSP6
Last active October 8, 2025 23:29
Show Gist options
  • Save EduardoSP6/8bf460b772cd6c15e8746ae3db287d3e to your computer and use it in GitHub Desktop.
Save EduardoSP6/8bf460b772cd6c15e8746ae3db287d3e to your computer and use it in GitHub Desktop.
How to create a custom auth provider in Laravel

How to create a custom auth provider in Laravel

Imagine the following situation: in an API implemented in PHP and Laravel, we need to create an entity for users who are authenticated in another specific API, which provides only user data and not an authentication token.

This is a fairly common situation for most developers these days, and if you haven't experienced it yet, rest assured, that day will come soon.

Well, in this case, it's recommended that, in addition to implementing an internal authentication layer without using the User model, we also implement authentication token generation and user session management.

So, what we'll use is:

Let's start by installing the tymon/jwt-auth library using the command:

composer require tymon/jwt-auth

Run the following command to publish the package config file:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

You should now have a config/jwt.php file that allows you to configure the basics of this package.

Now we run the command below to generate the JWT token's private key:

php artisan jwt:secret

This command will generate an env variable named JWT_SECRET.

Now we create a class to represent the authenticated user by implementing the JWTSubject interface and the getJWTIdentifier and getJWTCustomClaims methods from the installed library.

The getJWTIdentifier method asks us to return a unique entity field for identification.

While in the getJWTCustomClaims method, we define which user data will be stored in the token.

We also create the static createForProvider method to create an instance of our user in the provider we will create next.

<?php

namespace App\Domain\Auth;

use Tymon\JWTAuth\Contracts\JWTSubject;

/**
 * @property int $id
 * @property string $registration
 * @property string $name
 * @property string $email
 */
class CustomUser implements JWTSubject
{
    private int $id;
    private string $registration;
    private string $name;
    private string $email;

    public function __construct(int $id, string $registration, string $name, string $email)
    {
        $this->id = $id;
        $this->registration = $registration;
        $this->name = $name;
        $this->email = $email;
    }

    public function getJWTIdentifier()
    {
        return $this->id;
    }

    public function getJWTCustomClaims(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
            'registration' => $this->registration,
            'unity' => $this->unity,
            'userType' => $this->userType,
        ];
    }
    
    /**
    * Method for auth provider.
    */
    public static function createForProvider(int $id): static
    {
        return new self(
            id: $id,
            registration: "",
            name: "",
            email: "",
        );
    }

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getRegistration(): string
    {
        return $this->registration;
    }

    /**
     * @param string $registration
     */
    public function setRegistration(string $registration): void
    {
        $this->registration = $registration;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @param string $name
     */
    public function setName(string $name): void
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getEmail(): string
    {
        return $this->email;
    }

    /**
     * @param string $email
     */
    public function setEmail(string $email): void
    {
        $this->email = $email;
    }
}

Now, we create a class called CustomUserProvider that implements a UserProvider interface internal to Laravel's authentication layer.

<?php

namespace App\Domain\Auth\Provider;

use App\Domain\Auth\CustomUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;

class CustomUserProvider implements UserProvider
{

    public function retrieveById($identifier): CustomUser|null
    {
        return CustomUser::createForProvider($identifier);
    }

    public function retrieveByToken($identifier, $token)
    {
        return null;
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
        // TODO: Implement updateRememberToken() method.
    }

    public function retrieveByCredentials(array $credentials)
    {
        return null;
    }

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

We now register the provider in Laravel's AuthServiceProvider file.

<?php

namespace App\Providers;

use App\Domain\Auth\Provider\CustomUserProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot(): void
    {
        $this->registerPolicies();

        Auth::provider('custom', function($app, array $config) {
            return new CustomUserProvider();
        });
    }
}

We now modify the config/auth.php file as follows:

<?php

return [
    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'custom',
        ],
    ],

    'providers' => [
        'custom' => [
            'driver' => 'custom',
            'model' => \App\Domain\Auth\CustomUser::class,
        ],
    ],
];

Now we just need to create the routes and the Controller for authentication requests.

  • Routes: routes/api.php:
<?php

use App\Http\Api\Controllers\Auth\AuthController;

Route::prefix('v1')->group(function () {
    Route::prefix('auth')->group(function () {
        Route::post('login', [AuthController::class, 'login']);
        Route::middleware('auth:api')->group(function () {
            Route::post('logout', [AuthController::class, 'logout']);
            Route::get('me', [AuthController::class, 'me']);
        });
    });
});
  • Controller:
<?php

namespace App\Http\Api\Controllers\Auth;

use App\Domain\Auth\LoginException;
use App\Domain\Auth\CustomUser;
use App\Http\Api\Requests\Auth\LoginRequest;
use App\Http\Api\Resources\Auth\CustomUserResource;
use App\Http\Api\Resources\Auth\LoginResource;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use Tymon\JWTAuth\Facades\JWTAuth;

class AuthController extends Controller
{

    public function login(LoginRequest $request): LoginResource|JsonResponse
    {
        try {
            // Call external API
            $response = Http::post('https://external.api.com/login', [
                'email' => $request->email,
                'password' => $request->password,
            ]);
            
            if ($response->failed()) {
                return response()->json(['error' => 'Invalid credentials'], 401);
            }
            
            $data = $response->json();
            
            $user = new CustomUser(
                id: $data['id'],
                registration: $data['registration'],
                name: $data['name'],
                email: $data['email']
            );

            // generate the token
            $token = JWTAuth::fromUser($user);

            // Here I save the user information in a cache configured for Redis. (optional)
            Cache::put("user:" . $user->getRegistration(), $user, config('session.lifetime'));

            return new LoginResource($user, $token);
        } catch (LoginException $ex) {
            return response()->json([
                'message' => $ex->getMessage()
            ], ResponseAlias::HTTP_UNAUTHORIZED);
        } catch (Exception $e) {
            return response()->json([
                'message' => $e->getMessage()
            ], ResponseAlias::HTTP_UNAUTHORIZED);
        }
    }

    public function me(): CustomUserResource
    {
        $payload = JWTAuth::parseToken()->getPayload();

        /** @var CustomUser $user */
        $user = Cache::get("user:" . $payload->get("registration"));

        return new CustomUserResource($user);
    }

    public function logout(): JsonResponse
    {
        $payload = JWTAuth::parseToken()->getPayload();

        Cache::forget("user:" . $payload->get("registration"));

        auth()->guard('api')->logout();

        return response()->json([
            "message" => "You are done"
        ]);
    }
}

  • Resources:

LoginResource:

<?php

namespace App\Http\Api\Resources\Auth;

use App\Domain\Auth\CustomUser;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class LoginResource extends JsonResource
{
    public function __construct(protected CustomUser $user, protected string $accessToken)
    {
        parent::__construct($this);
    }

    public function toArray(Request $request): array
    {
        return [
            'user' => new CustomUserResource($this->user),
            'accessToken' => $this->accessToken,
            'tokenType' => 'bearer',
            'expires' => config('jwt.ttl') * 60
        ];
    }
}

CustomUserResource:

<?php

namespace App\Http\Api\Resources\Auth;

use App\Domain\Auth\CustomUser;
use Illuminate\Http\Resources\Json\JsonResource;

class CustomUserResource extends JsonResource
{

    public function __construct(protected CustomUser $user)
    {
        parent::__construct($this);
    }

    public function toArray($request): array
    {
        return [
            'id' => $this->user->getId(),
            'name' => $this->user->getName(),
            'email' => $this->user->getEmail(),
            'registration' => $this->user->getRegistration(),
        ];
    }
}

That's it! We now have an independent security layer and session management for the logged-in user.

You can customize the remaining settings in the config/jwt.php file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment