Skip to content

Releases: bordoni/integration-workos

1.0.8

Choose a tag to compare

@bordoni bordoni released this 01 Jul 17:58
1.0.8
c543117

Added

  • Change email from the WorkOS Users page (#33) — adds a "Change email" action to the WorkOS → Users admin page, alongside "Open in WorkOS" and "Send password reset". When a privileged user (edit_users) changes another account's email it now commits immediately, skipping the emailed verification step that self-service still uses; the admin path also skips rate limiting, sends no notification, surfaces real conflicts, and logs email_change.admin_changed. Removes the now-unused change_email_admin_bypass_verification option.

Fixed

  • 500 in admin password-reset when profile is empty (#33) — an empty profile (the common case) fataled the endpoint instead of resolving to the default login profile. The profile sanitize callback no longer hands the request object back as the sanitized value.

1.0.7

Choose a tag to compare

@bordoni bordoni released this 23 Jun 21:35
1.0.7
218409d

What's Changed

  • Fix: Active environment no longer reverts on settings save. Saving WorkOS settings no longer resets workos_active_environment to staging when the active environment field is absent from the form. The sanitizer now preserves the current active environment, including legacy workos_global['active_environment'] fallback state. (#34)

Full Changelog: 1.0.6...1.0.7

1.0.6

Choose a tag to compare

@bordoni bordoni released this 11 Jun 20:49
4669742

Added

  • Change email (WorkOS-verified, conflict-guarded) (#22) — self-service and admin-triggered email-change flow. Self-service [workos:change-email] shortcode, a "Change email" row action under the WorkOS column on wp-admin/users.php, and a "Change Email" panel on the user-edit screen. The new address is confirmed via hashed (HMAC-SHA256 + wp_salt('auth')) single-use, expiry-bounded tokens stored as _workos_pending_email_change user_meta and validated with hash_equals (WorkOS's email_verification endpoints can't verify a pending address); the old address simultaneously gets a one-click cancel link. Three new REST endpoints (initiate / confirm / cancel) are capability-gated, per-IP + per-user rate-limited, and enumeration-safe. Configurable conflict policy (block default, allow_orphan, merge_request) prevents silently overwriting another local WP user. Commits to WorkOS first (update_user) then mirrors into WordPress, guarded by a 60-second in-progress transient that short-circuits the webhook fan-back. Adds 8 filters, 5 actions, 7 activity-log event types, and 40 WPUnit tests. See docs/change-email.md.
  • Per-form magic-code registration toggles (CONS-350) (#25) — two independent per-environment checkboxes on the WorkOS settings page gate whether an unknown email signing in with a magic code provisions a new account. Email Code Registration controls the default sign-in form (/login/); Legacy Email Code Registration controls the legacy form (/login/legacy/, slug filterable via workos_legacy_profile_slug). When a form's toggle is off, POST /auth/magic/send skips the WorkOS call for unknown addresses and still returns 200 ok: true, and POST /auth/magic/verify early-returns a generic 400 workos_authkit_invalid_code instead of proceeding to LoginCompleter/UserSync — closing the account-enumeration leak where send previously returned 404 workos_authkit_no_account. Both options (allow_magic_code_registration, allow_legacy_magic_code_registration) default to true, preserving historical behavior.

Fixed

  • Username generation could exhaust memory and block provisioning (CONS-513) (#27) — when a popular email local part (e.g. info@) already had a deep info_1, info_2, … collision chain, generate_username() probed it one username_exists() call at a time, hydrating a WP_User into the object cache per probe until the request OOM'd — so those users could log in via WorkOS but never got a WordPress account. Collisions are now resolved with a suffix derived from sha256( email ) (e.g. info_48f25), which is unique by construction: at most 2 lookups regardless of chain depth, and the same email always derives the same username, so crash retries and replayed webhooks converge instead of minting new names. Existing accounts keep their current usernames.
  • WorkOS → Users settings would not save — the "Sync Roles to WorkOS" button rendered its own <form> inside the Settings API <form action="options.php">. Browsers don't allow nested forms: the inner </form> closed the outer settings form early, leaving the "Save Settings" submit button outside the form, so saving did nothing. The sync form is now deferred to admin_footer (body level, like the Create Organization modal) and its button is wired back via the HTML5 form="workos-role-sync-form" attribute, so the settings form stays intact and saves normally.

Full Changelog: 1.0.5...1.0.6

1.0.5

Choose a tag to compare

@bordoni bordoni released this 19 May 16:27
1.0.5
283ab4d

Added

  • WorkOS → Users admin page (CONS-273) — new submenu under WorkOS that mounts a paginated, searchable React list of WorkOS users for the active environment. Each row exposes an "Open in WorkOS" deep-link straight to the user's Dashboard page (https://dashboard.workos.com/{env}/users/{id}/details), where the per-user "Re-enable email" action lives. Gated by manage_options. Backed by GET /wp-json/workos/v1/admin/users, which proxies Api\Client::list_users() with sanitized limit (1..100), cursor (after/before), email substring, and organization_id, and enriches each user record with a server-computed dashboard_url. List-only by design: WorkOS does not expose a public REST endpoint for the "Re-enable email" action as of this release.
  • Admin-triggered WorkOS password reset (#21, CONS-287) — privileged WP users can now send a WorkOS password-reset email on behalf of any linked user via POST /wp-json/workos/v1/admin/users/{id}/password-reset. Gated by edit_user($id) (so the same route covers self-service from the shortcode), rate-limited per-IP and per-target, and writes a password_reset.admin_sent event to the activity log. Triggered from three surfaces: a Send password reset row action under the WorkOS column on wp-admin/users.php (clustered with "View in WorkOS" and "Re-sync" via the new workos_user_list_column_actions filter), a Password Reset panel on the user-edit screen, and a [workos:password-reset] shortcode that toggles between admin-of-other (user="…") and self-service modes.
  • redirect_url parameter on admin + public reset endpoints — the value is validated same-host against home_url(), baked into the URL handed to WorkOS for the email, and passed back to the React shell so the user lands on the chosen page after a successful reset. Fixes the long-standing CONS-287 ("reset password redirects users to Kadence Central") where the post-reset URL was unconfigurable.
  • In-site React reset page — reset emails now point at /workos/login/{profile}?token=…&redirect_to=… instead of wp-login.php. The old wp-login.php?workos_action=reset-password URL still resolves cleanly via LoginTakeover, so reset emails already in users' inboxes continue to work.
  • Password strength + confirmation on reset — the ResetConfirm step now requires the user to enter the new password twice and scores it via WordPress's wp.passwordStrength.meter (zxcvbn) in real time. The submit button stays disabled until the two fields match and the score reaches Strong (zxcvbn ≥ 3). Site name and common words are passed as the zxcvbn disallowed list.
  • Auto-login after password reset — new per-profile toggle auto_login_after_reset (default: on). When enabled, a successful reset authenticates the user via the shared LoginCompleter (so MFA / organization selection / entitlement gates still apply) and lands them on the validated post-reset redirect. Toggle off restores the prior "Continue to sign in" card.
  • WP password mirror on reset — when a WorkOS reset succeeds and the WorkOS user is linked to a WP user (via _workos_user_id meta), the plugin also runs wp_set_password() on the linked WP user with the new plaintext, keeping the WordPress password-fallback paths (?fallback=1, wp_authenticate, REST app passwords) in sync. Best-effort: unlinked users no-op cleanly; a write failure never fails the reset response.
  • Skeleton placeholders on every AuthKit surface — wp-login.php takeover, /workos/login/{profile}, and [workos:login] shortcode now paint a card-shaped skeleton with shimmering placeholder rows the moment the page lands, instead of a blank gap while the React bundle downloads and client.bootstrap() resolves. Heights match the hydrated form exactly so the swap is a flicker, not a layout jump. Shimmer animates only background-position (no transforms, no opacity) and is disabled under prefers-reduced-motion.

Full Changelog: 1.0.4...1.0.5

1.0.4

Choose a tag to compare

@bordoni bordoni released this 14 May 18:23
afb510d

Fixed

  • Logged-out screen leaks classic wp-login (#18) — both LoginTakeover::should_takeover() and Login::maybe_redirect_to_authkit() short-circuited on ?loggedout=true, so the native "you have been logged out" screen rendered with the wp-login username/password field. Legacy customers misread that as a still-working classic sign-in and kept trying to use it even though their credentials live in WorkOS. The bypasses are removed; with a custom_path-equipped default profile, /wp/wp-login.php?loggedout=true now 302s to /login/?loggedout=true via the existing redirect_to_custom_path forwarder. The ?fallback=1, ?workos=0, and action=logout|lostpassword|rp|... bypasses are unchanged.
  • Password reset URLs HTML-encoded in WorkOS emails (#17) — wp_login_url() runs through the login_url / home_url filters; a host-side filter piped it through esc_url(), encoding &&amp;. WorkOS emailed the URL verbatim, so reset links arrived as ?workos_action=…&amp;profile=…&amp;token=… and broke. The plugin now decodes HTML entities (named and numeric) in build_password_reset_url() before POSTing to WorkOS. A regression wpunit test fails without the fix.
  • Infinite login redirect loops (#15) — auth redirects now send no-cache / no-store headers so a cached redirect response cannot trap the browser in a redirect cycle when the user navigates back to a previously visited URL.
  • Unchecking auth methods or MFA factors did not persist (#14) — update_profile in Admin/LoginProfiles/RestApi.php ran array_replace_recursive(\$existing, \$params) so partial PUTs only needed to send touched fields. For numerically-indexed lists that function merges by key and never removes entries that exist in the base but are absent from the override, so unchecking `oauth_google` on a profile with `['password', 'magic_code', 'oauth_google']` left `oauth_google` at index 2 of the merged result. After the merge, `methods` and `mfa.factors` are now explicitly overwritten from the incoming payload when the client sent them — `array_key_exists()` is used (not `isset()`) so an empty array (uncheck-everything save) is honored. Scalars and associative branches like `signup`, `branding`, and the `mfa` envelope itself were unaffected.

Tests

  • Adds two cases in `AuthKitLoginProfilesRestApiTest`: `test_update_methods_payload_replaces_existing` and `test_update_mfa_factors_payload_replaces_existing` — start with multiple values, PUT with fewer, assert dropped values are gone in both the response and a fresh repository read; the second also asserts the sibling `mfa.enforce` survives the partial merge.
  • Adds a wpunit regression covering HTML-entity decoding in `build_password_reset_url()`.

Full Changelog: 1.0.3...1.0.4

1.0.3

Choose a tag to compare

@bordoni bordoni released this 12 May 19:05
1.0.3
2477dad

Fixed

  • organization_selection_required recovery. AuthKit login flows (magic code, password, MFA verify, invitation accept) and the legacy /workos/callback OAuth handler now recover transparently when WorkOS returns organization_selection_required. LoginCompleter::complete() accepts the WP_Error directly, looks up the Profile's pinned organization_id (with Config::get_organization_id() as fallback), and re-authenticates via the urn:workos:oauth:grant-type:organization-selection grant. Users no longer see "The user must choose an organization to finish their authentication." when their Login Profile has an org pinned.

  • Auto-enroll for legacy users. When the pinned org isn't in WorkOS's candidate list AND a matching WP user already exists locally, the plugin self-heals by creating the WorkOS organization membership and retrying the org-selection grant. The flow strictly requires the authenticated user_id from the WorkOS error body — if it's absent, the request is rejected with workos_authkit_pinned_org_mismatch rather than guessing via a list_users(email=…) lookup that can collide on shared addresses. Membership creation and entity_already_exists short-circuits log via workos_log() so prod incidents have a breadcrumb.

  • Unified OAuth callback. /workos/callback (Login::handle_callback) now routes the authenticate_with_code result through LoginCompleter::complete(), so it shares the same recovery + MFA gating + post-login bookkeeping as the AuthKit REST endpoints. Legacy AuthKit-redirect callbacks (no profile slug in state) pass $honor_profile_redirect = false so the state-supplied redirect_to still wins over the default profile's post_login_redirect — pre-refactor contract preserved.

  • Plugin::$version synced. The duplicate private string $version on WorkOS\Plugin was still reporting 1.0.1. Updated to 1.0.3 so any runtime consumer (admin badge, debug output) reports the real shipped version.

Tests

  • New AuthKitLoginCompleterOrgSelectionTest (9 wpunit cases) covering each recovery branch.
  • New ApiClientAuthKitTest::test_authenticate_with_organization_selection_uses_grant.

Internal

  • New Client::authenticate_with_organization_selection( $pending_auth_token, $organization_id ).

Full changelog: 1.0.2...1.0.3

1.0.2

Choose a tag to compare

@bordoni bordoni released this 11 May 23:12
1.0.2
754245d

Added

  • WordPress password fallback — when WorkOS rejects a password, the
    auth endpoint can now retry against wp_authenticate() to cover users
    whose passwords were never synced to WorkOS (e.g. accounts that existed
    before the plugin was installed). On success the WP user is linked /
    synced to WorkOS and, by default, the password is written through to
    WorkOS so future logins authenticate directly. A new
    Require Email Confirmation on Fallback setting (option
    wp_password_fallback_email_confirmation) switches the post-fallback
    step to a magic-code email instead of syncing the plaintext password.
    Gated by the existing allow_password_fallback setting (default on).
  • wp-config.php constant seeder — defining WORKOS_* (or
    env-scoped WORKOS_{PRODUCTION|STAGING}_*) constants in wp-config.php
    now seeds the corresponding settings into the database on boot, so the
    admin UI reflects them and runtime reads stay on the existing options
    layer. Covers string credentials (WORKOS_CLIENT_ID, WORKOS_API_KEY,
    …), the new boolean toggles (WORKOS_ALLOW_PASSWORD_FALLBACK,
    WORKOS_WP_PASSWORD_FALLBACK_EMAIL_CONFIRMATION), and array values
    (WORKOS_REDIRECT_URLS). Skipped via a stored hash check
    (workos_constants_hash option) when nothing has changed — steady-state
    cost is one autoloaded get_option() call per request.

Fixed

  • REST nonce header — auth endpoints under /wp-json/workos/v1/auth/*
    now read the nonce from X-WorkOS-Nonce instead of X-WP-Nonce. The
    prior name collided with the header WP core and other plugins consume,
    which could cause the wrong nonce to be validated when multiple
    scripts attached to the same request. The bundled React shell sends
    the new header automatically; external clients calling these endpoints
    directly need to update their header name.

Tests

  • Adds ConfigSyncConstantsTest (11 wpunit cases) covering the new
    constant seeder: string / bool / array maps, env-specific override
    precedence, empty-string skip, the hash short-circuit, and the
    trailing in-memory cache reset. The fixture defines WORKOS_*
    constants in setUp(); because PHP cannot undefine a constant and
    Codeception 5 ignores PHPUnit's process-isolation directives, the
    class is tagged @group constants and CI now runs it as a dedicated
    codecept run invocation. Default slic run wpunit skips the group.

1.0.1

Choose a tag to compare

@bordoni bordoni released this 01 May 17:40
1.0.1
fd40224

Added

  • Organization tab — manual Refresh button next to the organization
    dropdown that re-fetches the WorkOS organization list on demand via the
    admin REST endpoint (GET /workos/v1/admin/profiles/organizations?refresh=1),
    bypassing the 5-minute transient cache. The dropdown is visually blocked
    with a spinner while the request is in flight; the previously selected
    organization is preserved across the refresh when it still exists.
  • ?refresh query parameter on
    GET /wp-json/workos/v1/admin/profiles/organizations — drops the shared
    workos_organizations_cache_{env} transient before fetching from WorkOS.

Fixed

  • Organization tab — fix "Save Settings" being blocked by an invalid hidden
    form control (org_name). The Create Organization Thickbox modal is now
    rendered at admin_footer so its inner <form> no longer nests inside
    the outer settings form, preventing HTML5 constraint validation on the
    hidden required field from aborting the parent form's submission.
  • Active environment — workos_active_environment is now the single source
    of truth for the active environment. The admin Settings UI wrote the
    user's selection to a standalone option while the runtime auth flow read
    from workos_global['active_environment'], so picking "Production"
    saved successfully but the runtime kept loading staging credentials and
    redirecting users to the staging AuthKit. Config::get_active_environment()
    / set_active_environment() now read and write the standalone option,
    with a one-time migration (db_version 2 → 3) that copies any legacy
    value out of workos_global and unsets it.

Version: 1.0.0

Choose a tag to compare

@bordoni bordoni released this 27 Apr 00:17
1.0.0
306cdb5

First stable release of Integration with WorkOS — enterprise identity for WordPress, powered by WorkOS.

Highlights

Custom AuthKit — WordPress-hosted login experience

A React login shell mounted inside WordPress. All WorkOS API calls stay server-side; the browser only ever talks to /wp-json/workos/v1/auth/*.

  • Login Profiles — admin-defined presets at WorkOS → Login Profiles (sign-in methods, pinned organization, signup / invitation / password-reset flows, MFA policy, branding, custom URL paths).
  • wp-login.php takeoveraction=login renders the React shell; everything else passes through to WordPress defaults.
  • Sign-in methods — email + password, magic code, social OAuth (Google, Microsoft, GitHub, Apple), passkey.
  • MFA — TOTP, SMS, WebAuthn / passkey. In-app enrollment, challenge, and verify. Per-profile mfa.enforce and factor allowlist.
  • Self-serve flows — sign-up with email verification, invitation acceptance, password reset — all in-app.
  • WorkOS Radar integration — browser action token forwarded on every server-side auth call.
  • Profile routing rules — ordered redirect_to glob / referrer_host / user_role matchers pick the right profile per request.
  • SlotFill extensibility — 10 named React slots for third-party plugins to inject UI without forking the shell.

Base platform

  • SSO via WorkOS AuthKit (redirect or headless), Directory Sync (SCIM) with provisioning + deprovisioning, Role Mapping WorkOS → WP, Organization Management with multisite support, Entitlement Gate.
  • Webhook processing — real-time user, organization, and directory events at /wp-json/workos/v1/webhook.
  • REST API authentication — Bearer token auth for headless / decoupled WordPress.
  • Activity & Audit Logging — local table viewer + WorkOS Audit Logs forwarding.
  • Role-based login & logout redirects, password-reset & registration redirects, login bypass via ?fallback=1.
  • Diagnostics page, Onboarding wizard, Admin Bar badge, WP-CLI commands for scripting and bulk operations.

Security & internationalization

  • Webhook signature verification, atomic invitation token consumption, MFA enforcement at challenge time, per-IP and per-email rate limits on every auth mutation, profile-scoped WP nonces.
  • Browser i18n via @wordpress/i18n with full integration-workos text-domain support.

Screenshots

Custom AuthKit login Login Profiles WorkOS settings Role mapping & redirects
Branded Custom AuthKit login Login Profiles list WorkOS settings Role mapping and redirects
Branded login your site visitors see — logo, brand color, and the sign-in methods you enable. Pick sign-in methods, pin an organization, set MFA policy, brand each card without code. Switch Production / Staging, manage API credentials, and pick the login mode. Map WorkOS roles to WP roles and send users to role-specific URLs.

Requirements

Installation

Download the attached .zip, then in WordPress admin go to Plugins → Add New → Upload Plugin. Activate, then visit Settings → WorkOS to add your API credentials. The Onboarding wizard at WorkOS → Onboarding walks through first-time setup.

Links