{"id":50410222,"url":"https://github.com/adrum/socialite-provider-oidc","last_synced_at":"2026-05-31T03:03:23.646Z","repository":{"id":360972009,"uuid":"1229875119","full_name":"adrum/socialite-provider-oidc","owner":"adrum","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-05T13:20:26.000Z","size":15,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T17:32:43.785Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adrum.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-05T13:16:27.000Z","updated_at":"2026-05-05T13:18:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/adrum/socialite-provider-oidc","commit_stats":null,"previous_names":["adrum/socialite-provider-oidc"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/adrum/socialite-provider-oidc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrum%2Fsocialite-provider-oidc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrum%2Fsocialite-provider-oidc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrum%2Fsocialite-provider-oidc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrum%2Fsocialite-provider-oidc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adrum","download_url":"https://codeload.github.com/adrum/socialite-provider-oidc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrum%2Fsocialite-provider-oidc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33717419,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T03:03:17.027Z","updated_at":"2026-05-31T03:03:23.639Z","avatar_url":"https://github.com/adrum.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenID Connect\n\n```bash\ncomposer require socialiteproviders/openidconnect\n```\n\nA generic [OpenID Connect](https://openid.net/connect/) provider for Laravel Socialite. The provider discovers its endpoints via the issuer's `/.well-known/openid-configuration` document, supports JWT signature verification (JWKS or a configured public key), nonce validation, and PKCE.\n\n## Installation \u0026 Basic Usage\n\nPlease see the [Base Installation Guide](https://socialiteproviders.com/usage/),\nthen follow the provider specific instructions below.\n\n### Add configuration to `config/services.php`\n\n```php\n'openidconnect' =\u003e [\n    'base_url'       =\u003e env('OIDC_BASE_URL'),\n    'client_id'      =\u003e env('OIDC_CLIENT_ID'),\n    'client_secret'  =\u003e env('OIDC_CLIENT_SECRET'),\n    'redirect'       =\u003e env('OIDC_REDIRECT_URI'),\n\n    // Optional: space-separated list of extra scopes to request in addition\n    // to the defaults ('openid email profile').\n    'scopes'         =\u003e env('OIDC_SCOPES'),\n\n    // Optional: when true, id_token signatures will be verified using the\n    // provider's JWKS (or 'jwt_public_key' below if set).\n    'verify_jwt'     =\u003e env('OIDC_VERIFY_JWT', false),\n\n    // Optional: PEM-encoded public key used to verify id_token signatures\n    // instead of fetching the JWKS.\n    'jwt_public_key' =\u003e env('OIDC_JWT_PUBLIC_KEY'),\n\n    // Optional: signing algorithm hint (e.g. RS256, RS512, ES256, PS256).\n    // Defaults to the alg in the id_token header, or RS256.\n    'jwt_algorithm'  =\u003e env('OIDC_JWT_ALGORITHM'),\n\n    // Optional: override the expected `iss` claim. Defaults to the issuer\n    // reported by the discovery document.\n    'issuer'         =\u003e env('OIDC_ISSUER'),\n\n    // Optional: client authentication method at the token endpoint.\n    // Accepts 'client_secret_basic' or 'client_secret_post'. If omitted,\n    // the provider picks whichever the IdP advertises (preferring basic).\n    'token_auth_method'        =\u003e env('OIDC_TOKEN_AUTH_METHOD'),\n\n    // Optional: default post-logout redirect URI used by the logout() helper.\n    'post_logout_redirect_uri' =\u003e env('OIDC_POST_LOGOUT_REDIRECT_URI'),\n\n    // Optional: TTL (in seconds) for the cached discovery document and JWKS.\n    // Defaults to 3600 (1 hour).\n    'cache_ttl'                =\u003e env('OIDC_CACHE_TTL'),\n\n    // Optional: clock skew leeway (in seconds) applied to exp/nbf/iat. 0 by default.\n    'clock_skew'               =\u003e env('OIDC_CLOCK_SKEW'),\n\n    // Optional: Guzzle connect/read timeouts for IdP calls. Defaults: 5s/10s.\n    'http_connect_timeout'     =\u003e env('OIDC_HTTP_CONNECT_TIMEOUT'),\n    'http_timeout'             =\u003e env('OIDC_HTTP_TIMEOUT'),\n],\n```\n\nThe Authorization Code Flow with PKCE is used by default (`response_type=code`, PKCE is enabled on the `Provider`).\n\n`base_url` is the OIDC issuer URL (for example `https://id.example.com`). The provider appends `/.well-known/openid-configuration` automatically.\n\n### Add provider event listener\n\n#### Laravel 11+\n\nIn Laravel 11, the default `EventServiceProvider` provider was removed. Instead, add the listener using the `listen` method on the `Event` facade, in your `AppServiceProvider` `boot` method.\n\n```php\nEvent::listen(function (\\SocialiteProviders\\Manager\\SocialiteWasCalled $event) {\n    $event-\u003eextendSocialite('openidconnect', \\SocialiteProviders\\OpenIDConnect\\Provider::class);\n});\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\nLaravel 10 or below\n\u003c/summary\u003e\nConfigure the package's listener to listen for `SocialiteWasCalled` events.\n\nAdd the event to your `listen[]` array in `app/Providers/EventServiceProvider`. See the [Base Installation Guide](https://socialiteproviders.com/usage/) for detailed instructions.\n\n```php\nprotected $listen = [\n    \\SocialiteProviders\\Manager\\SocialiteWasCalled::class =\u003e [\n        // ... other providers\n        \\SocialiteProviders\\OpenIDConnect\\OpenIDConnectExtendSocialite::class.'@handle',\n    ],\n];\n```\n\u003c/details\u003e\n\n### Usage\n\n```php\nreturn Socialite::driver('openidconnect')-\u003eredirect();\n```\n\n```php\n$user = Socialite::driver('openidconnect')-\u003euser();\n```\n\nThe returned user is populated from the `id_token` claims, falling back to the `userinfo` endpoint when the id_token does not contain an email. Mapped fields include `id` (`sub`), `email`, `name`, `nickname`, `given_name`, `family_name`, `idp`, `role`, and `groups`. The raw user also contains `id_token`, which you should stash in the session at login if you want to drive RP-initiated logout later.\n\n### RP-Initiated Logout\n\nIf the IdP advertises an `end_session_endpoint` in its discovery document, you can build a logout redirect:\n\n```php\n// In your login callback, stash the id_token so you can pass it back at logout.\nsession(['oidc_id_token' =\u003e $user-\u003euser['id_token']]);\n\n// In your logout controller:\nreturn Socialite::driver('openidconnect')\n    -\u003elogout(session('oidc_id_token'), route('home'));\n```\n\nMost IdPs require `id_token_hint` (the id_token from login) and a `post_logout_redirect_uri` that has been pre-registered with the client.\n\n### Token Revocation\n\nIf the IdP advertises a `revocation_endpoint` (RFC 7009), you can revoke an access or refresh token server-side — useful at logout to invalidate the refresh token immediately rather than waiting for it to expire:\n\n```php\n// In your login callback, stash the refresh_token alongside the id_token.\n$oidcUser = Socialite::driver('openidconnect')-\u003euser();\nsession([\n    'oidc_id_token'      =\u003e $oidcUser-\u003euser['id_token'],\n    'oidc_refresh_token' =\u003e $oidcUser-\u003erefreshToken,\n]);\n\n// In your logout controller:\nif ($refresh = session('oidc_refresh_token')) {\n    Socialite::driver('openidconnect')-\u003erevoke($refresh, 'refresh_token');\n}\n\nreturn Socialite::driver('openidconnect')\n    -\u003elogout(session('oidc_id_token'), route('home'));\n```\n\nThe second argument to `revoke()` is a hint (`access_token` or `refresh_token`) and defaults to `refresh_token`. Returns `true` on a successful (200/204) response.\n\n### Storing tokens persistently\n\nThe examples above use the session, which is fine when the user always logs out from the same browser session they logged in with. If you need refresh tokens to survive session expiry — for example, to call `revoke()` from a back-channel logout handler, or to refresh access tokens for background jobs — store them on the user record instead:\n\n```php\n// Migration\nSchema::table('users', function (Blueprint $table) {\n    $table-\u003etext('oidc_id_token')-\u003enullable();\n    $table-\u003etext('oidc_refresh_token')-\u003enullable();\n});\n\n// User model\nprotected $casts = [\n    'oidc_id_token'      =\u003e 'encrypted',\n    'oidc_refresh_token' =\u003e 'encrypted',\n];\n\n// Login callback\n$oidcUser = Socialite::driver('openidconnect')-\u003euser();\n$user = User::updateOrCreate(\n    ['email' =\u003e $oidcUser-\u003eemail],\n    [\n        'name'               =\u003e $oidcUser-\u003ename,\n        'oidc_id_token'      =\u003e $oidcUser-\u003euser['id_token'],\n        'oidc_refresh_token' =\u003e $oidcUser-\u003erefreshToken,\n    ],\n);\nAuth::login($user);\n\n// Logout controller\nif ($user-\u003eoidc_refresh_token) {\n    Socialite::driver('openidconnect')-\u003erevoke($user-\u003eoidc_refresh_token, 'refresh_token');\n}\n$idToken = $user-\u003eoidc_id_token;\n$user-\u003eupdate(['oidc_id_token' =\u003e null, 'oidc_refresh_token' =\u003e null]);\n\nreturn Socialite::driver('openidconnect')-\u003elogout($idToken, route('home'));\n```\n\nBoth tokens are bearer credentials — always encrypt them at rest (`encrypted` cast) and never expose them to the browser via JavaScript-readable storage.\n\n### Back-Channel Logout\n\nIf you register a `backchannel_logout_uri` with the IdP, the IdP will POST a signed `logout_token` to that URL whenever the user logs out elsewhere (for example, another client in the same SSO federation). Your app must verify the token and destroy the matching local session.\n\n#### Mapping IdP sessions to Laravel sessions\n\nThe IdP has no idea what your Laravel session ID is. It identifies the session by a `sid` claim that it mints itself and includes in both the id_token at login and the logout_token at logout. You are responsible for storing a mapping from `sid` → Laravel session ID at login time, and consulting it at logout time.\n\nPrerequisites:\n\n- The IdP advertises `backchannel_logout_session_supported: true` in its discovery document.\n- Your client registration has `backchannel_logout_session_required` enabled so the IdP actually includes `sid` in issued tokens.\n\nIf the IdP does not support `sid`, logout tokens will only carry `sub`. You can still implement back-channel logout — you just have to kill **all** of the user's local sessions instead of only the one that ended. That is correct but coarser: a user logged in on three devices will be signed out of all three when they log out of one.\n\n```php\n// Migration\nSchema::create('oidc_sessions', function (Blueprint $table) {\n    $table-\u003estring('sid')-\u003eprimary();             // the IdP's session id\n    $table-\u003estring('laravel_session_id');         // session()-\u003egetId()\n    $table-\u003eforeignId('user_id')-\u003econstrained();\n    $table-\u003etimestamps();\n    $table-\u003eindex('user_id');\n});\n```\n\n#### Recording the mapping at login\n\n```php\n$oidcUser = Socialite::driver('openidconnect')-\u003euser();\n// ...find or create $user, Auth::login($user)...\n\nif ($sid = $oidcUser-\u003euser['sid'] ?? null) {\n    DB::table('oidc_sessions')-\u003eupdateOrInsert(\n        ['sid' =\u003e $sid],\n        [\n            'laravel_session_id' =\u003e session()-\u003egetId(),\n            'user_id'            =\u003e $user-\u003eid,\n            'updated_at'         =\u003e now(),\n            'created_at'         =\u003e now(),\n        ],\n    );\n}\n```\n\n`sid` arrives via the raw id_token claims on the user (`$oidcUser-\u003euser['sid']`), not as a first-class field on the Socialite user — it's a session attribute, not a user attribute.\n\n#### Handling the logout POST\n\n```php\nRoute::post('/oidc/backchannel-logout', function (Request $request) {\n    try {\n        $claims = Socialite::driver('openidconnect')\n            -\u003everifyLogoutToken($request-\u003einput('logout_token'));\n    } catch (\\InvalidArgumentException $e) {\n        return response('', 400);\n    }\n\n    // Replay protection: refuse a jti we've already processed.\n    $jtiKey = 'oidc_logout_jti_'.$claims['jti'];\n    if (Cache::has($jtiKey)) {\n        return response('', 400);\n    }\n    Cache::put($jtiKey, true, now()-\u003eaddHour());\n\n    // Prefer sid (per-session); fall back to sub (all sessions for that user).\n    $rows = ! empty($claims['sid'])\n        ? DB::table('oidc_sessions')-\u003ewhere('sid', $claims['sid'])-\u003eget()\n        : DB::table('oidc_sessions')\n            -\u003ejoin('users', 'users.id', '=', 'oidc_sessions.user_id')\n            -\u003ewhere('users.oidc_sub', $claims['sub'] ?? '')\n            -\u003eselect('oidc_sessions.*')\n            -\u003eget();\n\n    $handler = Session::getHandler();\n    foreach ($rows as $row) {\n        $handler-\u003edestroy($row-\u003elaravel_session_id);\n    }\n\n    DB::table('oidc_sessions')\n        -\u003ewhereIn('laravel_session_id', $rows-\u003epluck('laravel_session_id'))\n        -\u003edelete();\n\n    return response('', 200);\n})-\u003ewithoutMiddleware(['web']); // no CSRF token or session cookie on IdP requests\n```\n\nThe `sub`-fallback path assumes you store the IdP's `sub` claim on the user record (e.g. in an `oidc_sub` column) at login. If you don't need that fallback — because your IdP always emits `sid` — you can skip it.\n\n`verifyLogoutToken()` validates the signature, `iss`, `aud`, `iat`/`exp`, `jti`, the required `events` claim, and the absence of a `nonce`. The caller is responsible for replay protection (de-duping `jti`) and actually invalidating the session.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadrum%2Fsocialite-provider-oidc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadrum%2Fsocialite-provider-oidc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadrum%2Fsocialite-provider-oidc/lists"}