Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Oauth2 Server Exception Manager Laravel Package

spomky-labs/oauth2-server-exception-manager

Converts League OAuth2 Server exceptions into proper HTTP responses for PHP applications. Provides customizable error rendering so your API returns consistent, standards-compliant OAuth2 error output instead of raw exceptions. Lightweight, PSR-friendly, and easy to integrate into any framework.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a micro-package, not a framework. It exists to do one thing: take an OAuth2 server exception (a Throwable that implements the OAuth2 server's OAuth2ExceptionInterface) and turn it into a PSR-7 ResponseInterface that is RFC 6749-compliant — the right HTTP status, application/json body with error / error_description / error_uri / state, and the right WWW-Authenticate header where applicable.

Before installing, do a reality check. The repository has 0 stars, 0 dependents, and the last commit predates the merger of Spomky-Labs' oauth2-server into web-token/jwt-framework. That means:

  • the Composer constraints will almost certainly fight a modern PHP / PSR-7 stack (zendframework/zend-diactoros 1.x, psr/http-message ^1.0, PHP ^5.5|^7.0);
  • you are adopting an orphaned package for ~150 lines of logic you could copy into your own src/.
composer require spomky-labs/oauth2-server-exception-manager
# if the solver refuses on PHP/PSR-7 constraints:
composer require spomky-labs/oauth2-server-exception-manager --ignore-platform-reqs
composer why-not spomky-labs/oauth2-server-exception-manager 1.0

Where to look first: the src/ directory, specifically the class that maps an exception to a response. Everything else in the repo is glue. Read it top to bottom — it is short — and note which exception classes it knows about and what status code it assigns to each.

Minimal usage is a single call:

use OAuth2\Server\Exception\OAuth2ExceptionInterface;

try {
    $response = $tokenEndpoint->issueToken($request);
} catch (OAuth2ExceptionInterface $e) {
    // the manager converts the exception into a PSR-7 response
    $response = $exceptionManager->handle($e);
}

First realistic use case: your /token endpoint currently leaks a 500 stack trace when the client sends bad credentials. Register the manager in your error-handling middleware and a client sending grant_type=password with a wrong secret gets:

HTTP/1.1 401 Unauthorized
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{"error":"invalid_client","error_description":"Client authentication failed"}

instead of an HTML exception page.

Implementation Patterns

As a PSR-15 / Stratigility middleware (the intended shape). The package was written for the Zend/Stratigility MiddlewarePipe era, but the logic is transport-agnostic — wrap it yourself:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use OAuth2\Server\Exception\OAuth2ExceptionInterface;

final class OAuth2ErrorMiddleware implements MiddlewareInterface
{
    public function __construct(private ExceptionManager $manager) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            return $handler->handle($request);
        } catch (OAuth2ExceptionInterface $e) {
            return $this->manager->handle($e, $request);
        }
    }
}

Register it outermost in the pipe for the token/authorize routes only. If it wraps unrelated routes, an OAuth2ExceptionInterface thrown by some library deep in a normal web request will silently become a 400 JSON body instead of a bug report.

Try/catch at the controller boundary. If you only have two or three OAuth2 endpoints, skip the middleware and catch at the handler. This is the least magical integration and the easiest to unit test:

public function token(ServerRequestInterface $request): ResponseInterface
{
    try {
        return $this->server->respondToAccessTokenRequest($request);
    } catch (OAuth2ExceptionInterface $e) {
        return $this->manager->handle($e);
    }
}

Custom mapping. When you add your own exception subtype (e.g. a rate-limit or tenant-specific error), extend the manager or hand it a map of exception class → status code rather than editing vendor code. Keep the mapping table in your own service provider/container definition.

Decorator for observability. The manager does not log. Wrap it:

final class LoggingExceptionManager
{
    public function handle(OAuth2ExceptionInterface $e): ResponseInterface
    {
        $this->logger->warning('oauth2.error', [
            'error'     => $e->getErrorCode(),
            'exception' => $e::class,
            // never log the description: it can contain user input
        ]);

        return $this->inner->handle($e);
    }
}

Testing. Assert on the wire format, not the class:

$response = $manager->handle(new InvalidGrantException('bad code'));

self::assertSame(400, $response->getStatusCode());
self::assertSame('application/json;charset=UTF-8', $response->getHeaderLine('Content-Type'));
self::assertSame('invalid_grant', json_decode((string) $response->getBody(), true)['error']);

Add one test per error code and per endpoint (/authorize errors are rendered differently from /token errors — see Gotchas).

Gotchas and Tips

It is not maintained — fork or inline it. With no stars and no dependents there is no one to upstream a fix to. If you keep it, vendor the ~150 lines into app/OAuth2/ and drop the dependency. The mapping table is the only thing you'd want to update anyway (RFC 6749 §4.1.2.1 for authorize-endpoint errors, §5.2 for token-endpoint errors).

It does not implement server_error gracefully. server_error and temporarily_unavailable per RFC must map to 500, and you should never surface the exception message for those; check that the description is a fixed generic string, not $e->getMessage(), or you may leak SQL/credential details to a client.

Different endpoints, different statuses and required headers. The same invalid_request is 400 everywhere, but invalid_client is 401 on the token endpoint only when using the Authorization header, otherwise 400. 401s need WWW-Authenticate; if the manager omits it, browsers and strict clients will misbehave. Diff the output against RFC 6749 §5.2 line by line.

Content-Type and cache headers are part of the contract. application/json;charset=UTF-8 (the charset is recommended, not required — clients sometimes match on it literally), plus Cache-Control: no-store and Pragma: no-cache on every token error.

PSR-7 pinning is the usual install failure. The package pulls an old Diactoros, which conflicts with anything expecting psr/http-message ^2.0. Symptoms: --ignore-platform-reqs "succeeds", then TypeError: Argument #1 must implement Psr\Http\Message\ResponseInterface at runtime. Verify with composer show psr/http-message and composer show | grep -i diactoros before you blame your own code.

Middleware ordering is the #1 debugging trap. If your exception is not being converted, the exception manager is registered after your router or inside the OAuth2 pipe rather than around it. Log the class name and status code from inside handle() — if it never fires, the exception is being caught upstream.

Don't use it as your global error handler. It only understands OAuth2 exceptions. Everything else (routing 404s, validation 400s, uncaught \RuntimeException) must go through your normal error handler, otherwise you will ship a service that returns {"error":"invalid_request"} for a missing route.

Security: error descriptions are attacker-visible. Anything derived from request input (scope names, client IDs, redirect URIs) must be escaped in the JSON body and must not be reflected into error_description if it contains user data — RFC says so, and the package will not stop you. Also verify the state parameter is echoed back on /authorize errors, otherwise third-party clients will silently drop the flow.

Extension point. The one genuinely useful hook is the mapping table. Keep it as data (array/PSR-11 config) so you can add insufficient_scope → 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="…" without subclassing.

Migration note. If this repository is on your radar, the living successor is web-token/jwt-framework's OAuth2 support (or league/oauth2-server, which bundles the equivalent exception-to-response logic in League\OAuth2\Server\Exception). Both are actively maintained; this package is not.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk