Skip to main content

Custom Authentication Services

This chapter will guide you through the process of creating your own custom authentication service. This allows you to integrate any authentication method you need.

Creating a Custom Auth Service

First, you need to create a new class that will contain your authentication logic. It's a good practice to place this class in the app/Services/Auth directory. For example, app/Services/Auth/MyCustomAuthService.php.

This class must implement at least the App\Services\Auth\Contract\AuthServiceInterface.

The Authentication Service Interfaces

There are three different interfaces that your authentication service can implement. Each interface adds a specific functionality to your service.

AuthServiceInterface

This is the basic interface that every authentication service must implement. It has one method: authenticate.

// app/Services/Auth/Contract/AuthServiceInterface.php
public function authenticate(Request $request): AuthenticatedUserInfo|Response;

This method is responsible for authenticating the user. It is called when a user tries to log in. If the authentication is successful, it must return an AuthenticatedUserInfo object containing the user's details. If the authentication fails (e.g., wrong credentials), the method should throw an AuthFailedException. For authentication methods that require redirecting the user to an external login page (like OIDC or Shibboleth), this method should return a Response object that performs the redirect. If a technical error occurs (e.g., the authentication provider is unreachable), the method should throw an exception.

Here is a minimal example of an AuthServiceInterface implementation:

// app/Services/Auth/MyCustomAuthService.php
use App\Services\Auth\Contract\AuthServiceInterface;
use App\Services\Auth\Value\AuthenticatedUserInfo;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class MyCustomAuthService implements AuthServiceInterface
{
public function authenticate(Request $request): AuthenticatedUserInfo|Response
{
// Your authentication logic here
if (/* user is authenticated */) {
return new AuthenticatedUserInfo(
'unique-user-id',
'username',
'User Display Name',
'employee'
);
}

return redirect()->route('login');
}
}

AuthServiceWithCredentialsInterface

This interface should be implemented if your authentication service uses a username and password. It adds two methods to your service: useCredentials and forgetCredentials.

// app/Services/Auth/Contract/AuthServiceWithCredentialsInterface.php
public function useCredentials(string $username, string $password): void;
public function forgetCredentials(): void;
  • useCredentials: This method receives the username and password entered by the user on the login form. You should store them to use in the authenticate method.
  • forgetCredentials: This method is called after the authentication attempt to clear the stored credentials.

By implementing this interface, your service can be used with the default login form.

AuthServiceWithLogoutRedirectInterface

Implement this interface if you need to redirect the user to a specific URL after they log out. This is common for services like OIDC or Shibboleth where the logout needs to happen on the identity provider's side.

It has one method: getLogoutResponse.

// app/Services/Auth/Contract/AuthServiceWithLogoutRedirectInterface.php
public function getLogoutResponse(Request $request): ?RedirectResponse;

This method should return a RedirectResponse to the logout URL.

AuthServiceWithPostProcessingInterface

This interface can be implemented to perform actions after a user has been successfully authenticated. This is useful for tasks like logging the login event, updating user data, or redirecting the user to a specific page based on certain conditions.

It has two methods: afterLoginWithUser and afterLoginWithoutUser.

// app/Services/Auth/Contract/AuthServiceWithPostProcessingInterface.php
public function afterLoginWithUser(User $user, Request $request): Response|null;
public function afterLoginWithoutUser(AuthenticatedUserInfo $userInfo, Request $request): Response|null;
  • afterLoginWithUser: This method is called after an existing user has been authenticated and the corresponding User model has been retrieved from the database. It receives the User object and the current Request. It can return a Response to redirect the user or null to continue the normal login flow.

  • afterLoginWithoutUser: This method is called when a user authenticates successfully, but does not yet exist in the local database. This is the case when a user logs in for the first time. It receives an AuthenticatedUserInfo object with the information from the authentication provider. It can return a Response to redirect the user or null to continue to the registration page.

Using the Auth Utils

The app/Services/Auth/Util directory contains some helpful utilities to make creating your auth service easier.

  • AuthServiceWithCredentialsTrait: You can use this trait to quickly implement the AuthServiceWithCredentialsInterface. It provides the useCredentials and forgetCredentials methods and stores the credentials in private properties.

    // app/Services/Auth/MyCustomAuthService.php
    use App\Services\Auth\Contract\AuthServiceInterface;
    use App\Services\Auth\Contract\AuthServiceWithCredentialsInterface;
    use App\Services\Auth\Util\AuthServiceWithCredentialsTrait;

    class MyCustomAuthService implements AuthServiceInterface, AuthServiceWithCredentialsInterface
    {
    use AuthServiceWithCredentialsTrait;

    public function authenticate(Request $request): AuthenticatedUserInfo|Response
    {
    // You can now access $this->username and $this->password
    // ...
    }
    }
  • AuthRedirectBuilder: This helper class can be used to build redirect responses, for example in your getLogoutResponse method. It allows you to easily add static query parameters as well as a list of build route urls as query parameters. This is useful to redirect the user back to a specific page after login/logout.

    $redirectResponse = AuthRedirectBuilder::build(
    baseUrl: 'https://example.com/callback', // If this is empty (e.g. because not configured) null will be returned
    routeQueryParams: [ 'next' => 'dashboard.home' ], // The full URL for the route "dashboard.home" will be added as the "next" query parameter
    queryParams: [ 'foo' => 'bar' ], // Static query parameters
    );
    // This will create a redirect response to:
    // https://example.com/callback?next=https%3A%2F%2Fexample.com%2Fdashboard&foo=bar
  • DisplayNameBuilder: This utility helps you build a display name for the user from one or more attributes. As an example you can combine first name and last name to create a full name:

      use App\Services\Auth\Util\DisplayNameBuilder;
    DisplayNameBuilder::build(
    'firstname,lastname',
    fn(string $attribute) => $userAttributes[$attribute] ?? null
    );

Complete Example

Here is a more complete example of a custom authentication service that uses credentials and provides a custom logout redirect.

// app/Services/Auth/MyCustomAuthService.php
use App\Models\User;
use App\Services\Auth\Contract\AuthServiceInterface;
use App\Services\Auth\Contract\AuthServiceWithCredentialsInterface;
use App\Services\Auth\Contract\AuthServiceWithLogoutRedirectInterface;
use App\Services\Auth\Contract\AuthServiceWithPostProcessingInterface;
use App\Services\Auth\Util\AuthServiceWithCredentialsTrait;
use App\Services\Auth\Value\AuthenticatedUserInfo;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;

class MyCustomAuthService implements
AuthServiceInterface,
AuthServiceWithCredentialsInterface,
AuthServiceWithLogoutRedirectInterface,
AuthServiceWithPostProcessingInterface
{
use AuthServiceWithCredentialsTrait;

public function __construct(private readonly LoggerInterface $logger)
{
}

public function authenticate(Request $request): AuthenticatedUserInfo|Response
{
// Authenticate the user with $this->username and $this->password
if (/* authentication successful */) {
return new AuthenticatedUserInfo($this->username, 'Display Name', 'user@example.com', 'employee');
}

// If no credentials are provided, or they are wrong, redirect to login
return redirect()->route('login');
}

public function getLogoutResponse(Request $request): ?RedirectResponse
{
// Redirect to a custom logout page
return redirect('https://my-auth-provider.com/logout');
}

public function afterLoginWithUser(User $user, Request $request): ?Response
{
$this->logger->info('User ' . $user->username . ' logged in successfully.');
// No redirect needed, so return null
return null;
}

public function afterLoginWithoutUser(AuthenticatedUserInfo $userInfo, Request $request): ?Response
{
$this->logger->info('New user ' . $userInfo->username . ' authenticated for the first time.');
// No redirect needed, so return null
return null;
}
}

Registering Your Service

To use your new authentication service, you need to tell the application to use it. This is done via the AUTHENTICATION_METHOD environment variable in your .env file.

Set the value of this variable to the fully qualified class name of your service:

AUTHENTICATION_METHOD="App\Services\Auth\MyCustomAuthService"

The application will then automatically use your custom service for authentication.

IMPORTANT: Make sure your service class is autoloadable by Composer!