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

Laravel Oidc Server Laravel Package

admin9/laravel-oidc-server

Laravel OIDC Server provides an OpenID Connect provider for Laravel apps, with discovery, JWKS, authorization code flow, and token endpoints. Add standards-based SSO and identity to your Laravel project with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites. You need a Laravel app that already runs Passport (composer require laravel/passport, php artisan passport:install) with a working user provider. This package is an extension of Passport, not a replacement — the /oauth/token, /oauth/authorize, and client-management plumbing still come from Passport itself.

Install and publish.

composer require admin9/laravel-oidc-server
php artisan vendor:publish --provider="Admin9\OidcServer\OidcServiceProvider"
php artisan migrate   # only if the package ships extra tables

If the tagged publish fails, list tags first: php artisan vendor:publish and pick the oidc / oidc-config entry interactively.

Where to look first, in this order:

  1. config/oidc.php (or whatever the published file is named) — signing keys, issuer URL, enabled endpoints, claim mappings.
  2. php artisan route:list --path=oauth and php artisan route:list --path=.well-known — this is the fastest way to see exactly which endpoints the package registered and under what middleware. Do this before reading any docs.
  3. The package's src/ directory — with 6 stars and 0 dependents, the source is the documentation. Budget an hour to read the controllers for Discovery, JWKS, and UserInfo.

First use case to validate: get the discovery document to render correctly.

curl -s https://your-app.test/.well-known/openid-configuration | jq

If issuer, authorization_endpoint, token_endpoint, jwks_uri, and userinfo_endpoint are all absolute URLs pointing at your real host, you are 80% of the way there. Then verify JWKS:

curl -s https://your-app.test/oauth/jwks | jq '.keys[0] | {kty, alg, kid, use}'

You should get an RSA public key with a non-empty kid. If kid is missing, most OIDC clients will still work but key rotation becomes impossible — flag it early.

Implementation Patterns

1. Register clients the Passport way, then attach OIDC scopes. A client is only an OIDC client if it requests the openid scope. Create the OAuth client normally, then ensure openid, profile, and email exist as scopes:

// in a seeder or an admin action
Passport::tokensCan([
    'openid'  => 'Authenticate via OpenID Connect',
    'profile' => 'Read your name and profile',
    'email'   => 'Read your email address',
]);

$client = DB::table('oauth_clients')->insertGetId([
    'name'          => 'Acme Dashboard',
    'redirect'      => 'https://acme.test/auth/callback',
    'personal_access_client' => false,
    'password_client'        => false,
    'revoked'       => false,
]);

2. Authorization Code + PKCE for SPAs and mobile. The package does not add a new grant type — the SPA hits Passport's /oauth/authorize, gets a code, and exchanges it at /oauth/token. The difference after installing this package is that the token response includes an id_token alongside the access and refresh tokens when openid was requested. Verify that by decoding the JWT at jwt.io or with firebase/php-jwt.

3. Customize claims in one place, not in controllers. The most common day-to-day task is "the ID token should contain department" or "UserInfo should stop leaking phone". The package will have a claims resolver / user-info transformer you can bind. Look for an interface in src/Contracts and bind your implementation:

// AppServiceProvider::register()
$this->app->bind(
    \Admin9\OidcServer\Contracts\ClaimResolver::class,
    \App\Oidc\CompanyClaimResolver::class,
);

Keep this class pure — take a User, return an array. Do not do auth()->user() inside it; the resolver is often called from a token-introspection or userinfo request where the "current" user is not the subject.

4. Scope-gated claims. OIDC clients expect email to appear only when the email scope was granted. Drive the resolver off the token's scopes rather than off the user's database columns:

public function resolve(User $user, array $scopes): array
{
    $claims = ['sub' => (string) $user->id];

    if (in_array('email', $scopes, true)) {
        $claims['email'] = $user->email;
        $claims['email_verified'] = $user->hasVerifiedEmail();
    }

    if (in_array('profile', $scopes, true)) {
        $claims['name'] = $user->name;
    }

    return $claims;
}

5. Resource servers use introspection, not UserInfo. For service-to-service checks, POST the token to the introspection endpoint with the client credentials in HTTP Basic auth. Cache the result for the token's remaining lifetime — introspection is a network hop on every request otherwise.

6. RP-initiated logout. Wire your app's logout to hit the package's end-session endpoint with id_token_hint and a whitelisted post_logout_redirect_uri. The whitelist matters: unvalidated post-logout redirects are an open-redirect class bug, and reviewers will look for it.

7. Testing. Prefer issuing a real token over Passport::actingAs() when asserting OIDC behaviour, because the claims pipeline only runs on real token issuance. A cheap contract test is to boot the app in a test case and assert the discovery document's issuer equals config('app.url').

Gotchas and Tips

  • Pin the version and vendor the source. Six stars and zero dependents means you are the QA team. Add an exact constraint ("admin9/laravel-oidc-server": "1.0.*") and be prepared to fork. Check the GitHub issues tab before upgrading.
  • Passport version lockstep. Passport changed client model resolution (Passport::useClientModel) and key handling across v10–v12. If the package's service provider assumes a specific Passport major, installation on a newer Passport will fail at boot with a class-not-found rather than a clean error. Confirm in composer.json's constraints.
  • issuer behind a proxy. Almost every deployment bug is issuer resolving to http://localhost or an internal hostname. Set APP_URL correctly and configure TrustProxies so $request->getSchemeAndHttpHost() is right. The discovery document is cached by clients — a wrong issuer poisons them for hours.
  • JWKS and kid. Clients cache JWKS aggressively. If you rotate Passport keys, publish the new key in JWKS before signing with it, and keep the old kid listed for overlap. Never rotate both keys at once.
  • openid scope is the on/off switch. A token issued without it is an ordinary OAuth token with no ID token and (usually) a 403 from UserInfo. When a client reports "no id_token", check the granted scopes before anything else.
  • Revocation is shallow. Revoking an access token does not revoke its refresh token, and revoking a token does not end a browser SSO session. If you need "log out everywhere", you need your own session/token-family bookkeeping on top.
  • Token introspection requires client authentication. A missing Authorization: Basic header yields a generic 401 that looks like a bad token. Distinguish the two in your logs.
  • CORS on UserInfo. If a browser-side SPA calls UserInfo directly, you need CORS on that path. The package will not publish a CORS config for you; add it to config/cors.php's paths.
  • Route collisions. If you already have routes under /oauth/* or a /.well-known handler, confirm load order with php artisan route:list. Duplicate route names silently shadow.
  • Always php artisan config:cache after editing the OIDC config and re-run route:list — a cached config is the single most common cause of "my change did nothing".
  • Extension point to bookmark: the claim resolver binding. It is the least invasive seam and lets you upgrade the package without touching your customisation. Avoid subclassing the package's controllers; they are not a stable API at this maturity level.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin