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 Colony Laravel Package

thecolony/oauth2-colony

OAuth2 Colony is a League OAuth 2.0 client provider for authenticating with The Colony. It integrates into PHP and Laravel apps, handling authorization redirects, token exchange, and user retrieval through a simple, framework-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

thecolony/oauth2-colony is a framework-agnostic league/oauth2-client provider that speaks OpenID Connect. It is not a Laravel package — there is no service provider, facade, or Socialite driver — so in a Laravel app you bind it yourself and drive it from a controller. That is actually the day-to-day shape: one container binding, two routes (redirect + callback), and a session slot for state/nonce/PKCE verifier.

Install and orient yourself

composer require thecolony/oauth2-colony

Read in this order:

  1. README.md — constructor options, especially the discovery/issuer URL and the PKCE flag.
  2. src/ — the provider class (typically TheColony\OAuth2\Client\Provider\Colony) extending League\OAuth2\Client\Provider\AbstractProvider, plus any ResourceOwner/id_token value object.
  3. composer.json — confirm the league/oauth2-client version it pins; that determines whether you get getAuthorizationUrl() option passthrough and PKCE support for free.

First use case: log a user in and read their profile

use TheColony\OAuth2\Client\Provider\Colony;

$provider = new Colony([
    'clientId'     => config('services.colony.client_id'),
    'clientSecret' => config('services.colony.client_secret'),
    'redirectUri'  => route('colony.callback'),
    'issuer'       => config('services.colony.issuer'), // discovery root
]);

That's the whole mental model. Everything else — discovery, JWKS, id_token validation — hangs off that object.

Implementation Patterns

Container binding (do this once)

// app/Providers/AppServiceProvider.php
$this->app->singleton(\TheColony\OAuth2\Client\Provider\Colony::class, fn () => new \TheColony\OAuth2\Client\Provider\Colony([
    'clientId'     => config('services.colony.client_id'),
    'clientSecret' => config('services.colony.client_secret'),
    'redirectUri'  => route('colony.callback'),
    'issuer'       => config('services.colony.issuer'),
]));

Add the four keys to config/services.php so config:cache works in production.

Redirect route: state + nonce + PKCE

OIDC gives you three defenses; use all three even though only state is strictly "required" by the spec.

public function redirect(Colony $provider)
{
    $verifier  = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
    $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
    $nonce     = bin2hex(random_bytes(16));

    session([
        'colony.state'    => $provider->getState(),
        'colony.verifier' => $verifier,
        'colony.nonce'    => $nonce,
    ]);

    return redirect($provider->getAuthorizationUrl([
        'scope'                => ['openid', 'profile', 'email'],
        'code_challenge'       => $challenge,
        'code_challenge_method'=> 'S256',
        'nonce'                => $nonce,
    ]));
}

getAuthorizationUrl(array $options) merges options into the query string, so this works regardless of whether the provider has a first-class PKCE setter. If the README documents a pkce constructor flag instead, prefer that and drop the manual challenge.

Callback: validate, exchange, verify, map

public function callback(Request $request, Colony $provider)
{
    abort_unless(hash_equals(session()->pull('colony.state'), (string) $request->query('state')), 403);
    abort_if($request->query('error'), 403, $request->query('error_description'));

    $token = $provider->getAccessToken('authorization_code', [
        'code'          => $request->query('code'),
        'code_verifier' => session()->pull('colony.verifier'),
    ]);

    $idToken = $token->getValues()['id_token'] ?? null;
    // Verify signature against JWKS + iss/aud/exp/nonce. Signature name varies —
    // check README; typical shape:
    $claims = $provider->verifyIdToken($idToken, session()->pull('colony.nonce'));

    $user = User::updateOrCreate(
        ['colony_sub' => $claims['sub']],
        ['name' => $claims['name'] ?? null, 'email' => $claims['email'] ?? null]
    );

    Auth::login($user);
    return redirect()->intended('/dashboard');
}

Key the local user on sub, not email. Emails change; sub is the durable OIDC subject.

Resource owner vs. id_token claims

If you only need identity, prefer the verified id_token claims — no extra round trip. Use $provider->getResourceOwner($token) when you want live profile data (fresh name/avatar) or a UserInfo endpoint that returns more than the id_token. Map whichever you pick into the same internal user record.

Multi-environment / stateless variants

For an API issuing tokens to a SPA, you still own the session or a short-lived signed cookie for state/verifier/nonce. Don't drop them just because the backend is stateless — losing nonce silently disables replay protection on the id_token.

Gotchas and Tips

  • Maturity risk is the headline. One star, zero dependents, single maintainer, first release in 2026. Pin an exact version ("thecolony/oauth2-colony": "x.y.z") rather than ^x.y, skim src/ before shipping to production, and be prepared to fork. The dependency itself is MIT and small, so the fork cost is low — but audit it rather than assume a battle-tested provider.
  • Discovery is a network call. Construction or first use may fetch .well-known/openid-configuration. Do not do that per request. Cache the discovery doc (and the JWKS) in Laravel's cache, keyed by issuer, with a TTL and a stale-while-revalidate fallback so an outage at the IdP doesn't take down login for users who already have a session.
  • JWKS rotates. Cache keys but honor kid lookup and refresh on an unknown kid rather than failing the login. A hardcoded/pinned key set is the classic bug six months in.
  • redirect_uri must match byte-for-byte, including trailing slash and http vs https. Behind a proxy, route() may generate http:// — set TrustProxies and URL::forceScheme('https'), otherwise the value you send differs from the one registered on the IdP.
  • state and nonce are single-use. session()->pull() (not get()) so a replayed callback fails. Wrap the check in hash_equals, and never abort without clearing the session values.
  • openid scope is mandatory for an id_token to be returned at all. Requesting profile email alone gives you an access token and no claims, which looks like "verification is broken" but is really a scope bug.
  • Clock skew. exp/iat/nbf validation fails intermittently if the IdP and your server drift. Check the provider's leeway option; if it has none and drift is a problem, fix NTP rather than disabling validation.
  • Confidential clients should still use PKCE. client_secret alone doesn't protect the authorization code on a shared redirect. Sending both is spec-legal and strictly better.
  • Testing: inject a MockHttpClient (HttpMockClient from league/oauth2-client) into the provider, or bind a MockProvider in the container under a test env. Stub the discovery document and JWKS as fixtures so tests never touch the network; then assert your controller stores/consumes state/verifier/nonce correctly.
  • Extension points: subclass the provider and override getBaseAuthorizationUrl(), getResourceOwnerDetailsUrl(), or the token URL when you need to work around a non-conforming deployment, and register your subclass in the container instead. That keeps the upstream package upgradable.
  • Log the failure modes, not the tokens. Log error/error_description from the callback, the kid on JWKS misses, and claim validation failures — never the raw id_token or code_verifier.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core