kinde-oss/kinde-auth-php
kinde-oss/kinde-auth-php is the official Kinde SDK for PHP, making it easy to add secure authentication to PHP apps. It supports OAuth/OIDC login, registration, logout, social and passwordless sign-in, and token handling with minimal setup.
composer require kinde-oss/kinde-auth-php. There is no Laravel service provider to discover, no migrations, and no published Blade views—this is a framework-agnostic OIDC/OAuth2 client, so expect to write the Laravel glue yourself.config/services.php and .env, never env() calls in code: KINDE_DOMAIN/issuer, KINDE_CLIENT_ID, KINDE_CLIENT_SECRET, KINDE_REDIRECT_URI, KINDE_POST_LOGOUT_REDIRECT_URI, and scopes (openid profile email offline_access). Use route('kinde.callback') for the redirect URI so every environment produces its own exact URL.GET /auth/redirect, GET /auth/callback, POST /auth/logout—handled by one controller. Redirect builds the Kinde auth URL with state (and PKCE if supported), stores state in the session, and redirects. Callback validates state, exchanges the code for tokens, validates the ID token, then calls Auth::login() on a local user keyed by the sub claim.users table with kinde_id (the sub), email, name, and a JSON column for roles/org claims. Protect the app with Laravel's standard auth middleware; you do not need a custom guard for a normal session-based web app.dd()s the callback claims. Once you see sub, email, aud, iss, exp, and any org/role claims, refactor into a service class and controller.KindeService bound as a singleton in a service provider. Controllers and middleware should call your service, not SDK classes directly. This keeps SDK types out of your domain, gives you one place to add logging/retries, and makes tests trivial.Auth::login($user, remember: true). A custom UserProvider only pays off if you need stateless bearer-token auth for APIs.KindeAuthController with redirect, callback, and logout actions. Name the routes and derive the redirect URI from them; do not hardcode hosts in config.updateOrCreate(['kinde_id' => $claims['sub']], [...]). Treat sub as the only stable identity. Store org code, roles, and permissions as claims on login, and refresh them on each login rather than only at registration.AuthServiceProvider or a small mapper class. Avoid scattering if ($user->role === 'admin') checks; define Gate::define('manage-billing', ...) once and use @can/authorize() everywhere.Authorization: Bearer header, validates the access token against Kinde's JWKS (signature, iss, aud, exp, nbf), resolves the local user from sub, and sets the request user. Do not reuse the web session middleware for token auth, and do not accept ID tokens as API credentials.Auth::logout(), $request->session()->invalidate(), regenerateToken(), then redirect to Kinde's end-session endpoint with id_token_hint and post_logout_redirect_uri. Clearing only the Laravel session leaves the Kinde SSO session alive and the user gets silently signed back in.org_code on the authorization URL, persist the selected org in session, and scope every query by tenant. For an org switcher, re-run the auth redirect with the new org_code rather than mutating local state only.KindeService in the container for feature tests, and test the callback with a locally signed test token or a mocked token exchange. Assert that invalid state and expired codes redirect to a friendly error page rather than a 500.www all matter. APP_URL misconfiguration and php artisan config:cache baking stale values are the usual culprits. Run config:clear after changing Kinde settings, and never call env() outside config files.kid, check iss, aud, exp, and nbf with a small clock skew, and refetch JWKS when an unknown kid appears. Decoding a JWT without verification is not authentication.sub does not. Do not auto-link an existing local account by email unless the claim is verified and you understand the account-takeover risk.Secure, HttpOnly, and SameSite=Lax. The callback is a top-level GET redirect, so Lax works; SameSite=Strict can drop the state cookie. Behind a proxy or load balancer, configure TrustProxies or Laravel will generate http:// redirect URIs.Cache::lock() around refresh, or refresh in one middleware and let other requests wait.openid breaks the ID token; missing offline_access means no refresh token. Request scopes explicitly and keep them minimal.localStorage or expose the client secret to a browser. For SPAs and mobile, use Authorization Code + PKCE through a backend-for-frontend or the native SDK, not this server SDK with a secret.Login/Logout events for audit logging and user provisioning rather than stuffing that logic into the controller.How can I help you explore Laravel packages today?