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

Openid Connect Laravel Package

ronvanderheijden/openid-connect

OpenID Connect authentication for Laravel. Add OIDC login to your app with discovery, authorization code flow, token handling, and user info support.

View on GitHub
Deep Wiki
Context7

Getting Started

The package turns a Laravel app that already uses Laravel Passport into an OpenID Connect Provider (OP) — it adds the discovery document, a JWKS endpoint, an ID token on top of Passport's access tokens, and a userinfo endpoint.

Minimal path:

  1. composer require ronvanderheijden/openid-connect (Passport must already be installed and migrated).
  2. Generate OAuth keys if you haven't: php artisan passport:install (or passport:keys for non-persisted keys).
  3. Publish and read the config file first — this is the single most important thing to look at, because almost everything (issuer, scopes, routing, claim mapping) is driven from there:
    php artisan vendor:publish --tag=openid-connect-config
    
  4. Set the issuer to the exact public base URL clients will use, then php artisan config:cache.
  5. Create a client with php artisan passport:client --redirect_uri=... and grant it the scopes you advertise.
  6. Smoke-test the discovery endpoint: curl https://your-app.test/.well-known/openid-configuration. If this document is correct, most standards-compliant clients will configure themselves from it.

First realistic use case: you have an existing Laravel SaaS with Passport-issued tokens, and a first-party SPA or mobile app (or a partner app) that wants "Sign in with our product" using the authorization code + PKCE flow, with an ID token the client can verify against your JWKS.

Implementation Patterns

Discovery-first integration. Let clients (and your own tests) bootstrap from /.well-known/openid-configuration rather than hardcoding endpoints. This is also the cheapest regression test you can write:

$this->getJson('/.well-known/openid-configuration')
    ->assertOk()
    ->assertJsonPath('issuer', config('app.url'))
    ->assertJsonStructure(['authorization_endpoint', 'token_endpoint', 'jwks_uri', 'scopes_supported', 'response_types_supported']);

Authorization code + PKCE for SPAs. Keep the SPA public (no secret), register the redirect URI exactly, and require the openid scope plus whatever profile scopes you advertise. The openid scope is what makes Passport issue an ID token at all — forgetting it is the classic "why is there no id_token?" issue.

Server-to-server / M2M. Use Passport's client credentials grant for machine tokens and reserve the OIDC flows for user-facing auth. The OIDC layer is about identity; don't try to force ID tokens into M2M flows.

Claims mapping. Put your identity claims in one place (a user-to-claims transformer or a custom userinfo response) and reuse it for both the ID token and the userinfo endpoint so the two never disagree. Add claims only when a client actually needs them — email, name, sub cover most integrations, and sub must be stable and never recycled.

Resource-server verification in sibling services. Second Laravel API consumes the access token via Passport's guard; it validates the JWT signature against your jwks_uri and checks iss and aud. Cache the JWKS on the consumer side with a sane TTL so you're not fetching keys on every request.

Testing. Use Passport::actingAs($user, ['openid']) for endpoint tests, and add one end-to-end test that walks the real authorize → code → token → userinfo sequence. The flows are stateful and cookie/session-dependent, so mocked unit tests miss most of the failure modes.

Multi-tenant / multi-guard. If you serve several brands from one codebase, prefer separate app instances (and therefore separate issuers and key sets) over dynamically switching the issuer per request. The discovery document is a single global route; per-tenant issuers get messy fast.

Gotchas and Tips

The 1.2.1 fix matters more than it looks. Prior to 1.2.1 the scopes_supported key in /.well-known/openid-configuration was emitted incorrectly. Strict clients (and some OIDC libraries that validate the metadata document before use) can fail to register the provider, or silently fall back to a default scope set. After upgrading to 1.2.1, re-fetch the discovery document and confirm scopes_supported is a proper array of the scopes you actually grant. If you had a client working around this by hardcoding scopes, this is a good moment to remove the workaround.

Keep scopes_supported in sync with reality. The advertised scopes come from config, but what's actually grantable comes from Passport. These drift silently — you advertise profile but no client has it enabled, and the authorize step rejects with an opaque error. Treat the config list as the source of truth and create/update Passport scopes to match.

Issuer mismatch is the #1 support ticket. Behind a load balancer or proxy, config('app.url') often resolves to http://internal-host. The issuer must byte-for-byte match the URL the client used to reach discovery, otherwise token validation fails with iss mismatch. Fix trusted proxies / APP_URL / force HTTPS scheme rather than patching the client.

Config cache. Changing issuer, scopes, or route prefixes without php artisan config:cache (in production) produces confusing stale behavior.

Keys and rotation. passport:keys output and the JWKS endpoint must agree. After rotating keys, keep the old public key in the JWKS until the longest-lived token has expired, or clients will reject tokens mid-flight. Also watch file permissions on storage/oauth-*.key — a readable-but-wrong key gives signature failures that look like client bugs.

Token endpoint is form-encoded. application/json bodies return confusing 400s. Same for the Authorization: Basic client auth path when the client is confidential.

Middleware and sessions. The authorize endpoint needs the session/web guard; the token endpoint must not. If you customise routes, keep that split or you'll get "unauthenticated" on token exchange or CSRF failures on authorize.

Debugging order. (1) discovery document, (2) jwks_uri returns a key with the expected kid, (3) decode the ID token at jwt.io and check iss/aud/exp/nonce, (4) only then look at application code. Clock skew of more than a minute between containers is a surprisingly common cause of token expired.

Extension points. The supported way to customise behaviour is the published config plus overriding the package's controllers/routes in your app rather than editing vendor code — the userinfo response and claim set are the usual things you'll want to replace. Check the package's route registration before adding conflicting routes, and keep the well-known paths unprefixed and publicly reachable (no auth middleware, no locale prefix).

Pinning. Given the package wraps Passport, pin both the package and laravel/passport in composer.json and read the changelog for both before a Laravel major upgrade — most breakage shows up as discovery or grant-type incompatibility rather than a PHP error.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle