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 Token Endpoint Laravel Package

spomky-labs/oauth2-server-token-endpoint

Ready-to-use OAuth2 token endpoint for PHP from spomky-labs. Integrates with the OAuth2 server to process token requests, validate grant types, and issue access tokens via a PSR-7 compatible, middleware-friendly implementation for secure API authorization and token lifecycle management.

View on GitHub
Deep Wiki
Context7

Getting Started

spomky-labs/oauth2-server-token-endpoint is a thin piece of the (now abandoned) Spomky-Labs OAuth2 server split: it provides the handler behind POST /token — the code that reads grant_type from a PSR-7 request, dispatches to a registered grant type, and returns a PSR-7 response. It ships no storage, no client repository, no grant types, and no Laravel service provider. Those come from spomky-labs/oauth2-server and its companion grant packages.

Before writing any code, treat the health signals as a design input: no stars, zero dependents, unmaintained. Prototype only if you have a hard constraint; otherwise evaluate Laravel Passport (framework-native) or league/oauth2-server (maintained standalone equivalent) first. If you proceed, pin exact versions and audit with composer why-not.

Minimal steps:

  1. composer require spomky-labs/oauth2-server spomky-labs/oauth2-server-token-endpoint (plus whichever grant packages you need).
  2. Read the parent package's README first — the token endpoint repo is mostly a usage surface for the server's grant/repository interfaces.
  3. Add a route and bridge Illuminate ⇄ PSR-7 (Laravel 11+ has Request::toPsrRequest() / Response::toPsrResponse() via nyholm/psr7):
// routes/api.php
Route::post('/oauth/token', function (Illuminate\Http\Request $request) {
    $psrRequest  = $request->toPsrRequest();
    $psrResponse = response()->toPsrResponse(); // empty PSR-7 response

    $endpoint = app(\OAuth2\Server\TokenEndpoint\TokenEndpointInterface::class);
    $result   = $endpoint->getResponse($psrRequest, $psrResponse);

    return response(
        (string) $result->getBody(),
        $result->getStatusCode(),
        $result->getHeaders()
    );
});
  1. First use case: issue an access token with the client_credentials grant — it is the least moving parts (no user table, no consent flow), so it validates your repository wiring, client authentication, and PSR-7 bridge in one go.

Implementation Patterns

Register grants explicitly, one per flow. The endpoint only dispatches; each grant type is a constructor-injected object carrying its repositories (client, access token, refresh token, user). A small service provider or config file that builds the endpoint with only the grants you actually offer is the right shape — every extra grant is attack surface (password and refresh_token especially).

$this->app->singleton(TokenEndpoint::class, function ($app) {
    $server = new TokenEndpoint();
    $server->addGrantType(new ClientCredentials(
        $app->make(ClientRepository::class),
        $app->make(AccessTokenRepository::class)
    ));
    // add Password, RefreshToken, AuthorizationCode only if needed
    return $server;
});

Keep the endpoint stateless. It reads the request, delegates, returns a response. Build it as a singleton; construct grant types once. Anything request-scoped (the authenticated client) lives in the PSR-7 request or the repositories, not on the endpoint.

Client authentication via HTTP Basic. RFC 6749 §2.3.1 expects Authorization: Basic base64(client_id:client_secret). Confirm the parent package reads that header off the PSR-7 request rather than Laravel’s auth guard — pass the raw header through the bridge unchanged.

Implement repository interfaces over Eloquent. Write small adapters (ClientRepository, AccessTokenRepository, RefreshTokenRepository) that satisfy the parent package’s interfaces and delegate to models. Store refresh tokens hashed, rotate on use, and revoke the whole chain on reuse — the package will not do this for you.

Let OAuth errors pass through. Failed grant attempts produce a PSR-7 JSON body with error, error_description, and a 400/401 status. Map that response straight to Laravel; do not let the exception handler rewrite it into a generic 500 or an HTML page.

Form-encoded, not JSON. Per RFC 6749 the token request body is application/x-www-form-urlencoded. Laravel handles that natively; JSON bodies will (probably) land as a null getParsedBody() and fail confusingly.

Test at the route level. Feature tests that POST form params to /oauth/token, assert the JSON envelope and status, and then assert DB state (token row created, refresh token rotated) catch nearly all integration bugs. Add one test per grant type plus a negative case for unsupported_grant_type.

Gotchas and Tips

  • The endpoint is 10% of the work. Grant types, repository adapters, PSR-7 bridging, and client/secret management are the other 90%, and they live in the parent package. Budget accordingly; if you stall, it is almost certainly the parent, not this repo.
  • Abandonment risk is real. No maintained PHP 8 guarantees, PSR-7 v1 vs v2 constraints, and possible conflicts with current Symfony/Laravel dependency trees. Run composer update --dry-run and composer why-not before adopting.
  • PSR-7 streams are one-shot. If you getContents() without rewinding, you get an empty string. Use (string) $response->getBody() or rewind() first when converting back to an Illuminate response.
  • getParsedBody() can be null. That happens when nothing parsed the body — check the Content-Type and that the bridge you chose actually parses form data into the PSR-7 request.
  • Set the response headers OAuth requires. Token responses must be Content-Type: application/json, Cache-Control: no-store, Pragma: no-cache. Verify the endpoint sets them; if not, add them when you build the Illuminate response.
  • Diagnose unsupported_grant_type first. An unregistered or misspelled grant_type yields that error, not a 500 — good confirmation that the endpoint is reachable and dispatching. A 500 usually means a repository or grant constructor is misconfigured.
  • Extension point: implement a custom flow by extending the parent’s abstract grant type and registering it with an identifier string; keep it in its own service provider and its own test.
  • Migration path is cheap here. Because the endpoint is a thin dispatcher, swapping to Passport or league/oauth2-server mostly means replacing the route and repository adapters — another reason to prefer those from the start for anything long-lived.
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast