{"id":48359599,"url":"https://github.com/wenisch-tech/keystone-oidc","last_synced_at":"2026-05-31T23:00:31.741Z","repository":{"id":348973352,"uuid":"1200576887","full_name":"wenisch-tech/keystone-oidc","owner":"wenisch-tech","description":"Keystone OIDC is a WordPress plugin that exposes standard OIDC / OAuth 2.0 endpoints so that other applications can authenticate users against your existing WordPress user database. No external identity provider or third-party service is required.","archived":false,"fork":false,"pushed_at":"2026-05-31T21:05:26.000Z","size":3609,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T21:23:45.772Z","etag":null,"topics":["oauth2","oidc-provider","wordpress","wordpress-oidc","wordpress-plugin"],"latest_commit_sha":null,"homepage":"","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/wenisch-tech.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-04-03T15:21:45.000Z","updated_at":"2026-05-31T21:05:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/wenisch-tech/keystone-oidc","commit_stats":null,"previous_names":["jfwenisch/wp-oidcserver","wenisch-tech/keystone-oidc"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/wenisch-tech/keystone-oidc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenisch-tech%2Fkeystone-oidc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenisch-tech%2Fkeystone-oidc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenisch-tech%2Fkeystone-oidc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenisch-tech%2Fkeystone-oidc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wenisch-tech","download_url":"https://codeload.github.com/wenisch-tech/keystone-oidc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wenisch-tech%2Fkeystone-oidc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33752286,"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":["oauth2","oidc-provider","wordpress","wordpress-oidc","wordpress-plugin"],"created_at":"2026-04-05T12:02:06.151Z","updated_at":"2026-05-31T23:00:31.712Z","avatar_url":"https://github.com/wenisch-tech.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿# Keystone OIDC\n\n[![Release](https://github.com/wenisch-tech/keystone-oidc/actions/workflows/ci.yml/badge.svg)](https://github.com/wenisch-tech/keystone-oidc/actions/workflows/ci.yml)\n[![Latest Release](https://img.shields.io/github/v/release/wenisch-tech/keystone-oidc?label=release)](https://github.com/wenisch-tech/keystone-oidc/releases/latest)\n\n![Keystone OIDC](.assets/banner-1544x500.png)\n\n\u003e Turn your WordPress site into a fully featured **OpenID Connect (OIDC) identity provider**.\n\n\n\nKeystone OIDC is a WordPress plugin that exposes standard OIDC / OAuth 2.0 endpoints so that other applications — dashboards, CLIs, mobile apps, or any OIDC-aware tool — can authenticate users against your existing WordPress user database.  \nNo external identity provider or third-party service is required.\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Admin UI walkthrough](#admin-ui-walkthrough)\n  - [Client list](#client-list)\n  - [Add / Edit a client](#add--edit-a-client)\n  - [Settings page](#settings-page)\n- [How it works](#how-it-works)\n  - [Authorization Code Flow](#authorization-code-flow)\n  - [Token lifetimes](#token-lifetimes)\n  - [PKCE support](#pkce-support)\n  - [Signing keys](#signing-keys)\n  - [Secret storage](#secret-storage)\n- [OIDC Endpoint reference](#oidc-endpoint-reference)\n- [Scopes \u0026 Claims](#scopes--claims)\n- [Releases \u0026 CI pipeline](#releases--ci-pipeline)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n| | |\n|---|---|\n| ✅ | **OIDC Authorization Code Flow** (RFC 6749 / OpenID Connect Core 1.0) |\n| ✅ | **PKCE** – S256 and plain code-challenge methods (RFC 7636) |\n| ✅ | **RS256 JWT** signed access tokens and ID tokens |\n| ✅ | **Refresh tokens** with single-use rotation |\n| ✅ | **OIDC Discovery** document (`/wenisch-tech/keystone-oidc/.well-known/openid-configuration`) for zero-config clients |\n| ✅ | **JWKS endpoint** so clients can verify tokens without any shared secret |\n| ✅ | **Multi-client management** – create and manage as many clients as needed |\n| ✅ | **Client secret reset** – secrets shown only once, stored hashed |\n| ✅ | **Consent screen** – users explicitly approve each application |\n| ✅ | **Signing key rotation** from the admin panel |\n| ✅ | **Automatic cleanup** of expired auth codes and revoked tokens |\n\n---\n\n## Requirements\n\n| Requirement | Minimum version |\n|---|---|\n| WordPress | 5.6 |\n| PHP | 7.4 |\n| PHP extension | `openssl` |\n| PHP extension | `json` |\n\nPretty permalinks must be **enabled** in WordPress (`Settings → Permalinks`). The plugin uses custom rewrite rules that do not work with the plain `?p=123` permalink structure.\n\n---\n\n## Installation\n\n### Option A – Upload the ZIP (recommended)\n\n1. Download the latest `keystone-oidc-x.y.z.zip` from the [Releases page](https://github.com/wenisch-tech/keystone-oidc/releases).\n2. In your WordPress admin go to **Plugins → Add New → Upload Plugin**.\n3. Choose the downloaded ZIP and click **Install Now**.\n4. Click **Activate Plugin**.\n\n### Option B – Manual file copy\n\n1. Download and unzip the release archive.\n2. Copy the `keystone-oidc` folder to `wp-content/plugins/`.\n3. Activate the plugin in **Plugins → Installed Plugins**.\n\n### What happens on activation\n\n- Three database tables are created: `{prefix}oidc_clients`, `{prefix}oidc_auth_codes`, `{prefix}oidc_tokens`.\n- A 2048-bit RSA key pair is generated and stored in WordPress options (private key encrypted via WordPress option storage; never exposed through any UI).\n- Rewrite rules for all OIDC endpoints are flushed automatically.\n\n---\n\n## Quick Start\n\nAfter activating the plugin you only need to do two things:\n\n**1. Create a client**\n\nGo to **OIDC Provider → Add Client** in the WordPress admin sidebar.\n\nFill in:\n- **Application Name** – a friendly label (e.g. \"My Dashboard\")\n- **Redirect URIs** – the callback URL(s) your application will use (one per line, e.g. `https://dashboard.example.com/auth/callback`)\n- **Allowed Scopes** – tick `profile` and/or `email` if your app needs user info beyond the basic identity\n\nClick **Create Client**. You will be shown the **Client ID** and **Client Secret** once – copy them now.\n\n**2. Configure your application**\n\nPoint your OIDC client library at the discovery URL:\n\n```\nhttps://your-wordpress-site.example.com/wenisch-tech/keystone-oidc/.well-known/openid-configuration\n```\n\nMost OIDC libraries (e.g. `openid-client` for Node.js, `python-jose`, Keycloak adapters, Dex, etc.) will read this document and configure all endpoints automatically.\n\nProvide the **Client ID** and **Client Secret** you copied in step 1, set the redirect URI to match what you registered, and request the `openid` scope (plus `profile` and/or `email` as needed).\n\nThat's it – your users can now sign in to external applications using their WordPress credentials.\n\n---\n\n## Admin UI walkthrough\n\n### Client list\n\n**OIDC Provider → Clients**\n\nDisplays all registered clients in a table with their name, client ID, allowed scopes, and creation date. The discovery URL is shown at the top of the page for convenience.\n\nFrom the list you can click **Edit** to open a client's detail page.\n\n### Add / Edit a client\n\n**Add a client:** OIDC Provider → Add Client  \n**Edit a client:** click **Edit** on the client list, or navigate to OIDC Provider → Clients and click a client name.\n\nThe edit screen has three sections:\n\n**Client Credentials** (edit view only)\n- Shows the **Client ID** with a copy button.\n- Shows the discovery URL.\n- Contains a **Reset Secret** button. Clicking it generates a new secret and invalidates the previous one. The new secret is displayed once on the following page.\n\n**Configuration form**\n- *Application Name* – displayed on the consent screen to users.\n- *Redirect URIs* – the allowed callback URLs after authorization. One URI per line. Must be an exact match (no wildcard).\n- *Allowed Scopes* – which scopes the client may request. `openid` is always included.\n\n**Danger Zone**\n- Permanently **Delete Client** and all its associated tokens.\n\n### Settings page\n\n**OIDC Provider → Settings**\n\nDisplays read-only information about the server:\n\n- Issuer URL (the WordPress site URL)\n- All endpoint URLs ready to copy\n- Current signing key ID (`kid`)\n- Plugin version\n\n**Rotate Signing Keys** – generates a new RSA-2048 key pair. All previously issued access tokens and ID tokens will immediately become invalid. Use this after a suspected key compromise.\n\n---\n\n## How it works\n\n### Authorization Code Flow\n\n```\n Client App                WordPress (OIDC Provider)           User\n     │                             │                             │\n    │── GET /wenisch-tech/keystone-oidc/oauth/authorize ──── │                             │\n     │   ?response_type=code       │                             │\n     │   \u0026client_id=…              │                             │\n     │   \u0026redirect_uri=…           │                             │\n     │   \u0026scope=openid profile     │                             │\n     │   \u0026state=…                  │                             │\n     │                             │── redirect to wp-login ──── │\n     │                             │   (if not logged in)        │\n     │                             │                             │\n     │                             │◄──── user logs in ──────── │\n     │                             │                             │\n     │                             │── show consent screen ───── │\n     │                             │                             │\n     │                             │◄── user clicks Allow ────── │\n     │                             │                             │\n     │◄── redirect to redirect_uri │                             │\n     │    ?code=…\u0026state=…          │                             │\n     │                             │                             │\n    │── POST /wenisch-tech/keystone-oidc/oauth/token ─────── │                             │\n     │   grant_type=authorization_code                           │\n     │   code=… redirect_uri=…     │                             │\n     │   client_id=… client_secret=│                             │\n     │                             │                             │\n     │◄── access_token + id_token  │                             │\n     │    + refresh_token ──────── │                             │\n     │                             │                             │\n    │── GET /wenisch-tech/keystone-oidc/oauth/userinfo ────── │                             │\n     │   Authorization: Bearer …   │                             │\n     │                             │                             │\n     │◄── { sub, name, email, … }  │                             │\n```\n\n1. The client redirects the browser to `/wenisch-tech/keystone-oidc/oauth/authorize` with the standard parameters.\n2. If the user is not logged in, WordPress's own login flow handles authentication and then redirects back to the authorize endpoint.\n3. A consent screen lists the scopes being requested and asks the user to **Allow** or **Deny**.\n4. On approval, a short-lived **authorization code** (valid 10 minutes, single-use) is issued and the browser is redirected to the client's `redirect_uri`.\n5. The client's back-end exchanges the code for tokens by calling `/wenisch-tech/keystone-oidc/oauth/token` (server-to-server).\n6. The plugin returns an **access token** (RS256 JWT), an **ID token** (RS256 JWT), and a **refresh token**.\n7. The client can call `/wenisch-tech/keystone-oidc/oauth/userinfo` at any time with the Bearer access token to retrieve up-to-date user claims.\n\n### Token lifetimes\n\n| Token | Lifetime |\n|---|---|\n| Authorization code | 10 minutes |\n| Access token (JWT) | 1 hour |\n| ID token (JWT) | 1 hour |\n| Refresh token | 30 days |\n\nRefresh tokens use **single-use rotation**: each time a refresh token is used, it is revoked and a new one is issued alongside a fresh access token.\n\n### PKCE support\n\n[Proof Key for Code Exchange](https://datatracker.ietf.org/doc/html/rfc7636) protects public clients (e.g. SPAs, mobile apps) that cannot safely store a client secret.\n\nWhen the authorization request includes a `code_challenge`, the plugin stores it alongside the auth code. The token endpoint then validates the `code_verifier` before issuing tokens. Both `S256` (SHA-256 hash, preferred) and `plain` methods are supported.\n\n### Signing keys\n\nOn activation the plugin generates an RSA-2048 key pair using PHP's `openssl` extension. The private key is stored in the WordPress options table. The public key is published through the JWKS endpoint (`/wenisch-tech/keystone-oidc/oauth/jwks`) so that any client or resource server can verify tokens independently without trusting the plugin directly.\n\nThe key ID (`kid`) in each JWT header lets clients efficiently look up the correct key from the JWKS when multiple keys are present during rotation.\n\n### Secret storage\n\nClient secrets are **never stored in plaintext**. When a secret is created or reset, the plugin:\n\n1. Generates a cryptographically random 64-character hex string.\n2. Hashes it with WordPress's `wp_hash_password()` (bcrypt).\n3. Stores only the hash in the database.\n4. Displays the plaintext secret **once** in the admin UI via a short-lived (5-minute) transient.\n\nAfter the page is loaded once, the plaintext is discarded and cannot be recovered — only reset.\n\n---\n\n## OIDC Endpoint reference\n\nAll URLs are relative to your WordPress site root (e.g. `https://example.com`).\n\n| Endpoint | Method | Path |\n|---|---|---|\n| **Discovery** | GET | `/wenisch-tech/keystone-oidc/.well-known/openid-configuration` |\n| **Authorization** | GET / POST | `/wenisch-tech/keystone-oidc/oauth/authorize` |\n| **Token** | POST | `/wenisch-tech/keystone-oidc/oauth/token` |\n| **UserInfo** | GET | `/wenisch-tech/keystone-oidc/oauth/userinfo` |\n| **JWKS** | GET | `/wenisch-tech/keystone-oidc/oauth/jwks` |\n\n### `GET /wenisch-tech/keystone-oidc/.well-known/openid-configuration`\n\nReturns the [OIDC Discovery document](https://openid.net/specs/openid-connect-discovery-1_0.html) as JSON. Point your OIDC client library at this URL for automatic configuration.\n\n```json\n{\n  \"issuer\": \"https://example.com\",\n  \"authorization_endpoint\": \"https://example.com/wenisch-tech/keystone-oidc/oauth/authorize\",\n  \"token_endpoint\": \"https://example.com/wenisch-tech/keystone-oidc/oauth/token\",\n  \"userinfo_endpoint\": \"https://example.com/wenisch-tech/keystone-oidc/oauth/userinfo\",\n  \"jwks_uri\": \"https://example.com/wenisch-tech/keystone-oidc/oauth/jwks\",\n  \"scopes_supported\": [\"openid\", \"profile\", \"email\"],\n  \"response_types_supported\": [\"code\"],\n  \"grant_types_supported\": [\"authorization_code\", \"refresh_token\"],\n  \"id_token_signing_alg_values_supported\": [\"RS256\"],\n  \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"client_secret_post\"],\n  \"code_challenge_methods_supported\": [\"S256\", \"plain\"]\n}\n```\n\n### `GET /wenisch-tech/keystone-oidc/oauth/authorize`\n\nStarts the authorization flow. Required parameters:\n\n| Parameter | Required | Description |\n|---|---|---|\n| `response_type` | ✅ | Must be `code` |\n| `client_id` | ✅ | The client's ID |\n| `redirect_uri` | ✅ | Must exactly match a registered URI |\n| `scope` | ✅ | Space-separated; must include `openid` |\n| `state` | recommended | Opaque value echoed back to prevent CSRF |\n| `nonce` | recommended | Included in the ID token to prevent replay |\n| `code_challenge` | PKCE | Base64url-encoded challenge |\n| `code_challenge_method` | PKCE | `S256` or `plain` |\n\n### `POST /wenisch-tech/keystone-oidc/oauth/token`\n\nExchanges an authorization code or refresh token for tokens.\n\n**Client authentication** – choose one:\n- HTTP Basic Authentication: `Authorization: Basic base64(client_id:client_secret)`\n- POST body parameters: `client_id` + `client_secret`\n\n**Authorization Code grant** (`grant_type=authorization_code`):\n\n| Parameter | Required | Description |\n|---|---|---|\n| `grant_type` | ✅ | `authorization_code` |\n| `code` | ✅ | Authorization code from the authorize endpoint |\n| `redirect_uri` | ✅ | Must match the value used in the authorize request |\n| `client_id` | ✅ | |\n| `code_verifier` | PKCE | Required if `code_challenge` was used |\n\n**Refresh Token grant** (`grant_type=refresh_token`):\n\n| Parameter | Required | Description |\n|---|---|---|\n| `grant_type` | ✅ | `refresh_token` |\n| `refresh_token` | ✅ | The refresh token |\n| `client_id` | ✅ | |\n\n**Response:**\n\n```json\n{\n  \"access_token\": \"\u003cJWT\u003e\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 3600,\n  \"id_token\": \"\u003cJWT\u003e\",\n  \"refresh_token\": \"\u003copaque string\u003e\",\n  \"scope\": \"openid profile email\"\n}\n```\n\n### `GET /wenisch-tech/keystone-oidc/oauth/userinfo`\n\nReturns claims for the authenticated user. Requires a valid Bearer token.\n\n```\nAuthorization: Bearer \u003caccess_token\u003e\n```\n\n### `GET /wenisch-tech/keystone-oidc/oauth/jwks`\n\nReturns the public RSA key in [JWKS format](https://datatracker.ietf.org/doc/html/rfc7517). Use this to verify RS256-signed JWTs without contacting the token endpoint.\n\n---\n\n## Scopes \u0026 Claims\n\n| Scope | Claims included |\n|---|---|\n| `openid` | `sub`, `iss`, `aud`, `iat`, `exp`, `jti` |\n| `profile` | `name`, `given_name`, `family_name`, `preferred_username` |\n| `email` | `email`, `email_verified` |\n\nThe `sub` claim is always the WordPress user's numeric ID (as a string), ensuring stable, durable identity.\n\n---\n\n## Releases \u0026 CI pipeline\n\nEvery push to `main` automatically bumps the version (patch by default, following [Conventional Commits](https://www.conventionalcommits.org/)) via the [CI workflow](.github/workflows/ci.yml) which:\n\n1. Computes the next semver tag and generates a changelog.\n2. Patches the `Version:` header in `keystone-oidc.php` and `Stable tag:` in `readme.txt`.\n3. Creates a `keystone-oidc-x.y.z.zip` archive with `keystone-oidc/` as the root folder (the layout WordPress expects).\n4. Publishes a GitHub Release with the ZIP attached as a downloadable asset.\n\nNo manual tagging is required — just push to `main` and the pipeline handles everything.\n\nThe resulting ZIP can be uploaded directly via **Plugins → Add New → Upload Plugin** in WordPress.\n\n---\n\n## Troubleshooting\n\n### Endpoints return 404\n\n- Make sure **pretty permalinks are enabled** (`Settings → Permalinks`). Save the permalinks page once after activating the plugin to flush rewrite rules.\n- If you recently activated the plugin and are still seeing 404s, visit `Settings → Permalinks` and click **Save Changes** to force a flush.\n\n### \"Invalid redirect_uri\" error\n\nThe redirect URI in the authorization request must be an **exact match** (including scheme, host, path, and any trailing slash) with one of the URIs registered for the client. Check for trailing slashes and `http` vs `https` differences.\n\n### \"Invalid client\" error\n\n- The `client_id` does not exist. Double-check the value you configured in your application.\n- If you recently deleted and recreated a client, update the client ID in your application.\n\n### Tokens fail signature verification\n\nThe public key in the JWKS may have changed since the token was issued (e.g. after a key rotation). Fetch the JWKS again to get the current public key. Most well-behaved OIDC clients do this automatically when they encounter an unknown `kid`.\n\n### \"openssl not found\" / keys not generating\n\nEnsure the `openssl` PHP extension is installed and enabled. On most Linux distributions:\n\n```bash\nphp -m | grep openssl\n```\n\nIf missing, install it (e.g. `sudo apt-get install php-openssl` on Debian/Ubuntu) and restart your web server.\n\n### Users are not redirected back after login\n\nThis can happen if WordPress's `wp_login_url()` filters the redirect URL. Ensure the `allowed_redirect_hosts` WordPress filter includes your site's domain, or that no security plugin is aggressively blocking redirect parameters.\n\n---\n\n## Contributing\n\n1. Fork the repository and create a feature branch.\n2. Make your changes following the [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/).\n3. Open a pull request against `main`.\n\nTo create a development release, push a tag:\n\n```bash\ngit tag v0.0.1-dev\ngit push origin v0.0.1-dev\n```\n\n---\n\n## License\n\nThis plugin is licensed under the [GNU General Public License v2 or later](https://www.gnu.org/licenses/gpl-2.0.html), the same license as WordPress itself.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwenisch-tech%2Fkeystone-oidc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwenisch-tech%2Fkeystone-oidc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwenisch-tech%2Fkeystone-oidc/lists"}