|
1 | | -# workos-for-wordpress |
| 1 | +# WorkOS for WordPress |
| 2 | + |
| 3 | +**Enterprise-grade authentication and user management for WordPress, powered by [WorkOS](https://workos.com).** |
| 4 | + |
| 5 | +Replace the default WordPress login with WorkOS AuthKit and unlock SSO, SAML, OIDC, social login, MFA, and centralized user management — all without writing a single line of code. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Why WorkOS for WordPress? |
| 10 | + |
| 11 | +WordPress powers millions of business-critical sites, but its built-in authentication wasn't designed for enterprise requirements. WorkOS for WordPress bridges that gap: |
| 12 | + |
| 13 | +- **Single Sign-On (SSO)** — Let users authenticate with their corporate identity provider via SAML or OIDC. |
| 14 | +- **AuthKit hosted login** — A polished, branded login experience with social login, email + password, Magic Auth, and MFA built in. |
| 15 | +- **Organization-based access control** — Restrict site access to members of a specific WorkOS organization. |
| 16 | +- **Role synchronization** — Map WorkOS organization roles to WordPress roles automatically. |
| 17 | +- **User lifecycle management** — Import existing WordPress users into WorkOS, or enforce entitlements and suspend users who lose access. |
| 18 | +- **Audit logging** — Track every login, logout, and access decision locally. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Requirements |
| 23 | + |
| 24 | +- WordPress 6.4 or later |
| 25 | +- PHP 8.1 or later |
| 26 | +- A [WorkOS account](https://workos.com) (free to start) |
| 27 | +- Composer (for installing dependencies) |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Installation |
| 32 | + |
| 33 | +1. **Download or clone** the plugin into your WordPress plugins directory: |
| 34 | + |
| 35 | + ```bash |
| 36 | + cd wp-content/plugins/ |
| 37 | + git clone https://github.com/AlwaysCuriousCo/workos-for-wordpress.git |
| 38 | + ``` |
| 39 | + |
| 40 | +2. **Install dependencies** via Composer: |
| 41 | + |
| 42 | + ```bash |
| 43 | + cd workos-for-wordpress |
| 44 | + composer install |
| 45 | + ``` |
| 46 | + |
| 47 | +3. **Activate the plugin** from the WordPress admin under Plugins. |
| 48 | + |
| 49 | +4. **Configure your credentials** under the new **WorkOS** menu in the admin sidebar. |
| 50 | + |
| 51 | +--- |
| 52 | + |
| 53 | +## Configuration |
| 54 | + |
| 55 | +### Quick Start |
| 56 | + |
| 57 | +1. Navigate to **WorkOS > Welcome** in your WordPress admin. |
| 58 | +2. Enter your **API Key** and **Client ID** from the [WorkOS Dashboard](https://dashboard.workos.com). |
| 59 | +3. Go to **WorkOS > Organization & Roles** and enter your **Organization ID**. |
| 60 | +4. In your WorkOS Dashboard, add your callback URL as a **Redirect URI**: |
| 61 | + ``` |
| 62 | + https://your-site.com/workos/callback |
| 63 | + ``` |
| 64 | +5. Visit **WorkOS > Diagnostics** to verify connectivity. |
| 65 | + |
| 66 | +That's it — your WordPress login page now redirects through WorkOS AuthKit. |
| 67 | + |
| 68 | +### Environment-Based Configuration |
| 69 | + |
| 70 | +For managed deployments, staging environments, or version-controlled infrastructure, you can define credentials as constants in `wp-config.php` instead of storing them in the database: |
| 71 | + |
| 72 | +```php |
| 73 | +define('WORKOS_API_KEY', 'sk_live_...'); |
| 74 | +define('WORKOS_CLIENT_ID', 'client_...'); |
| 75 | +define('WORKOS_ORGANIZATION_ID', 'org_...'); |
| 76 | +``` |
| 77 | + |
| 78 | +When constants are defined, the corresponding fields in the admin UI become read-only and display where the value is sourced from. |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +## Features |
| 83 | + |
| 84 | +### AuthKit Login |
| 85 | + |
| 86 | +When the plugin is configured, the WordPress login page (`wp-login.php`) automatically redirects to WorkOS AuthKit. Users authenticate through your configured providers — SSO, social login, email/password, Magic Auth — and are redirected back to WordPress with a session established. |
| 87 | + |
| 88 | +- **Automatic user provisioning** — New users are created in WordPress on first login with their WorkOS profile data. |
| 89 | +- **Profile sync** — First name, last name, and email are updated from WorkOS on each login. |
| 90 | +- **Session management** — WorkOS session IDs are tracked so logout properly revokes both the WordPress session and the AuthKit hosted session. |
| 91 | +- **Bypass mode** — Append `?workos_bypass` to `wp-login.php` to access the native WordPress login form (useful for emergency access). |
| 92 | + |
| 93 | +### Organization & Role Mapping |
| 94 | + |
| 95 | +Bind your WordPress site to a WorkOS organization and map WorkOS roles to WordPress roles. |
| 96 | + |
| 97 | +1. Go to **WorkOS > Organization & Roles**. |
| 98 | +2. Enter your Organization ID (or set it via `WORKOS_ORGANIZATION_ID` in `wp-config.php`). |
| 99 | +3. Add role mappings — for example, map the WorkOS `admin` role to the WordPress `administrator` role, and `member` to `editor`. |
| 100 | + |
| 101 | +On each login, the plugin fetches the user's organization membership from WorkOS and sets their WordPress role accordingly. Users without a mapped role keep their current role. |
| 102 | + |
| 103 | +### Organization Entitlement Gate |
| 104 | + |
| 105 | +When enabled, the entitlement gate requires users to have an **active membership** in your configured WorkOS organization before they can log in. Users who authenticate successfully via WorkOS but are not organization members are denied access with a clear message. |
| 106 | + |
| 107 | +Enable this under **WorkOS > Learning Mode** → "Organization Entitlement Gate." |
| 108 | + |
| 109 | +### Learning Mode |
| 110 | + |
| 111 | +Learning Mode is designed for adopting WorkOS on an **existing WordPress site** with established users. When enabled, it discovers your WordPress users and syncs them into WorkOS: |
| 112 | + |
| 113 | +- **Users not in WorkOS** are created with their WordPress profile data (email, first name, last name) and marked as email-verified. |
| 114 | +- **Users already in WorkOS** (matched by email) are linked to their WordPress account. |
| 115 | +- **Organization membership** is ensured for every synced user, with WordPress roles reverse-mapped to WorkOS roles. |
| 116 | + |
| 117 | +#### How to Use Learning Mode |
| 118 | + |
| 119 | +1. Go to **WorkOS > Learning Mode**. |
| 120 | +2. Enable Learning Mode. |
| 121 | +3. Review the list of pending users. |
| 122 | +4. Click **Sync All** to process all users, or sync individual users one at a time. |
| 123 | +5. Once all users are synced, disable Learning Mode. |
| 124 | + |
| 125 | +The sync runs sequentially with a built-in delay between API calls to respect rate limits. Progress is displayed in real time. |
| 126 | + |
| 127 | +#### Best Practices |
| 128 | + |
| 129 | +- **Back up your database** before running a batch sync. |
| 130 | +- **Test with a small group first** — sync a few users individually before running a full batch. |
| 131 | +- **Review role mappings** before syncing to ensure WordPress roles map correctly to WorkOS roles. |
| 132 | +- **Enable the Entitlement Gate** after syncing to enforce organization-based access going forward. |
| 133 | +- **Disable Learning Mode** once all users are imported — it is an onboarding tool, not a permanent setting. |
| 134 | + |
| 135 | +### Users Table Integration |
| 136 | + |
| 137 | +The plugin enhances the standard WordPress **Users** table (`wp-admin/users.php`) with: |
| 138 | + |
| 139 | +- **WorkOS status column** — Shows a badge for each user: |
| 140 | + - **Synced** (green) — User is fully synced with WorkOS and has an active organization membership. Hover to see the sync timestamp. |
| 141 | + - **Linked** (purple) — User has logged in via WorkOS but hasn't been processed by Learning Mode. |
| 142 | + - **Not synced** (gray) — No WorkOS association. |
| 143 | + - **Suspended** (red) — User was suspended because they are not entitled in WorkOS. Hover to see the reason. |
| 144 | + |
| 145 | +- **reSync action** — Appears in each user's row actions (visible to administrators): |
| 146 | + - **Learning Mode ON** → "reSync to WorkOS" — Pushes the user's data to WorkOS and ensures organization membership. |
| 147 | + - **Learning Mode OFF** → "reSync from WorkOS" — Verifies the user exists in WorkOS with an active organization membership. If they don't, the user is **suspended** (all WordPress roles are removed, preventing login). Their account and content are preserved. |
| 148 | + |
| 149 | +### Activity Tracking |
| 150 | + |
| 151 | +An optional local audit log that records authentication events. No data is sent externally. |
| 152 | + |
| 153 | +Enable it under **WorkOS > Usage** → "Enable activity tracking." |
| 154 | + |
| 155 | +**Tracked events:** |
| 156 | + |
| 157 | +| Event | Description | |
| 158 | +|---|---| |
| 159 | +| `login` | Successful authentication via WorkOS AuthKit | |
| 160 | +| `logout` | User logged out (WordPress + WorkOS session revoked) | |
| 161 | +| `login_failed` | Authentication error during the OAuth callback | |
| 162 | +| `login_denied` | User blocked by the organization entitlement gate | |
| 163 | +| `learning_mode_sync` | User synced to WorkOS via Learning Mode | |
| 164 | +| `learning_mode_error` | Error during Learning Mode sync | |
| 165 | +| `user_suspended` | User suspended after failing pull-mode reSync | |
| 166 | + |
| 167 | +The **Usage** page displays: |
| 168 | +- Login, logout, and failed login counts (last 30 days) |
| 169 | +- Unique user count (last 30 days) |
| 170 | +- A table of the 20 most recent events with user email, IP address, and timestamp |
| 171 | +- A button to clear all logged events |
| 172 | + |
| 173 | +### Diagnostics |
| 174 | + |
| 175 | +The **Diagnostics** page verifies your WorkOS configuration: |
| 176 | + |
| 177 | +- Tests connectivity to the WorkOS API by generating an authorization URL. |
| 178 | +- Displays your Client ID, API Key (last 4 characters only), Organization ID, Redirect URI, and generated Auth URL. |
| 179 | +- Indicates whether each value is sourced from the database or a `wp-config.php` constant. |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +## How It Works |
| 184 | + |
| 185 | +### Login Flow |
| 186 | + |
| 187 | +``` |
| 188 | +User visits wp-login.php |
| 189 | + → Redirected to WorkOS AuthKit |
| 190 | + → User authenticates (SSO / email / social / MFA) |
| 191 | + → WorkOS redirects to /workos/callback with authorization code |
| 192 | + → Plugin exchanges code for access token + user profile |
| 193 | + → [Optional] Entitlement gate checks org membership |
| 194 | + → WordPress user created or matched (by WorkOS ID, then email) |
| 195 | + → Role synced from WorkOS organization membership |
| 196 | + → WordPress session established |
| 197 | + → User redirected to their intended destination |
| 198 | +``` |
| 199 | + |
| 200 | +### Logout Flow |
| 201 | + |
| 202 | +``` |
| 203 | +User clicks Log Out in WordPress |
| 204 | + → WordPress session cleared |
| 205 | + → Plugin revokes WorkOS session server-side |
| 206 | + → User redirected to WorkOS logout endpoint |
| 207 | + → AuthKit hosted session cookie cleared |
| 208 | + → User returned to site homepage |
| 209 | +``` |
| 210 | + |
| 211 | +### ReSync (Pull Mode) Flow |
| 212 | + |
| 213 | +``` |
| 214 | +Admin clicks "reSync from WorkOS" on a user |
| 215 | + → Plugin looks up user by email in WorkOS |
| 216 | + → If not found → user suspended (roles removed) |
| 217 | + → If found, checks for active organization membership |
| 218 | + → If no active membership → user suspended |
| 219 | + → If active membership → meta updated, role synced, badge set to "Synced" |
| 220 | +``` |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## User Metadata Reference |
| 225 | + |
| 226 | +The plugin stores the following metadata on WordPress user records: |
| 227 | + |
| 228 | +| Meta Key | Set By | Description | |
| 229 | +|---|---|---| |
| 230 | +| `_workos_user_id` | AuthKit / Learning Mode | The user's WorkOS user ID | |
| 231 | +| `_workos_access_token` | AuthKit | OAuth access token (JWT) | |
| 232 | +| `_workos_refresh_token` | AuthKit | OAuth refresh token | |
| 233 | +| `_workos_session_id` | AuthKit | Session ID for logout | |
| 234 | +| `_workos_organization_id` | AuthKit / Learning Mode | Organization the user belongs to | |
| 235 | +| `_workos_role_slug` | AuthKit / reSync | Last synced WorkOS role slug | |
| 236 | +| `_workos_synced_at` | Learning Mode / reSync | Timestamp of last Learning Mode sync | |
| 237 | +| `_workos_suspended` | reSync (pull mode) | Flag indicating user is suspended | |
| 238 | +| `_workos_suspended_reason` | reSync (pull mode) | `not_found_in_workos` or `no_org_membership` | |
| 239 | +| `_workos_suspended_at` | reSync (pull mode) | Timestamp of suspension | |
| 240 | + |
| 241 | +--- |
| 242 | + |
| 243 | +## Bypass & Recovery |
| 244 | + |
| 245 | +If you are locked out due to a misconfiguration: |
| 246 | + |
| 247 | +1. **Bypass AuthKit** — Access `wp-login.php?workos_bypass` to use the native WordPress login form. |
| 248 | +2. **Disable via wp-config.php** — If the plugin prevents all access, deactivate it by renaming the plugin directory or adding to `wp-config.php`: |
| 249 | + ```php |
| 250 | + // Temporarily disable WorkOS plugin |
| 251 | + define('WORKOS_API_KEY', ''); |
| 252 | + ``` |
| 253 | +3. **WP-CLI** — Deactivate the plugin from the command line: |
| 254 | + ```bash |
| 255 | + wp plugin deactivate workos-for-wordpress |
| 256 | + ``` |
| 257 | + |
| 258 | +--- |
| 259 | + |
| 260 | +## Security |
| 261 | + |
| 262 | +- API keys and tokens are stored in the WordPress database using standard options and user meta. For enhanced security, define credentials in `wp-config.php` and restrict file permissions. |
| 263 | +- Access tokens are stored per-user in user meta and cleared on logout. |
| 264 | +- All AJAX handlers verify nonces and check `manage_options` capability. |
| 265 | +- The activity log stores IP addresses and user agents for audit purposes. Enable only if your privacy policy permits it. |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +## FAQ |
| 270 | + |
| 271 | +**Does this replace the WordPress login entirely?** |
| 272 | +By default, yes — `wp-login.php` redirects to AuthKit. You can always access the native form via `?workos_bypass`. |
| 273 | + |
| 274 | +**Can I use this on a multisite installation?** |
| 275 | +The plugin is designed for single-site WordPress installations. Multisite support is not currently included. |
| 276 | + |
| 277 | +**What happens if WorkOS is unreachable?** |
| 278 | +The callback will fail with an error message. Users can use the bypass parameter to access the native login form as a fallback. |
| 279 | + |
| 280 | +**Will existing users lose access?** |
| 281 | +No. Existing users are matched by email on first AuthKit login. Use Learning Mode to proactively sync users before enforcing the entitlement gate. |
| 282 | + |
| 283 | +**What happens to suspended users' content?** |
| 284 | +Suspended users retain their account, posts, and content. Only their WordPress roles are removed, which prevents login. Re-enable their access by re-syncing with Learning Mode enabled or by manually assigning a role. |
| 285 | + |
| 286 | +--- |
| 287 | + |
| 288 | +## License |
| 289 | + |
| 290 | +GPL-3.0-or-later. See [LICENSE](LICENSE) for details. |
| 291 | + |
| 292 | +--- |
| 293 | + |
| 294 | +## Credits |
| 295 | + |
| 296 | +Built by [Always Curious](https://alwayscurious.co). Powered by [WorkOS](https://workos.com). |
0 commit comments