{"id":304732,"date":"2026-06-01T20:49:48","date_gmt":"2026-06-01T20:49:48","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/qrauth-passwordless-social-login\/"},"modified":"2026-06-03T09:29:07","modified_gmt":"2026-06-03T09:29:07","slug":"qrauth-passwordless-social-login","status":"publish","type":"plugin","link":"https:\/\/fa.wordpress.org\/plugins\/qrauth-passwordless-social-login\/","author":16169944,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.1.23","stable_tag":"0.1.23","tested":"7.0","requires":"6.4","requires_php":"8.2","requires_plugins":null,"header_name":"QRAuth \u2013 Passwordless & Social Login","header_author":"QRAuth","header_description":"Passwordless and social login for WordPress powered by QRAuth web components.","assets_banners_color":"081b4a","last_updated":"2026-06-03 09:29:07","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/qrauth-io\/qrauth-passwordless-social-login","header_author_uri":"https:\/\/github.com\/aristech","rating":5,"author_block_rating":0,"active_installs":10,"downloads":156,"num_ratings":1,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.1.21":{"tag":"0.1.21","author":"aristech","date":"2026-06-01 20:49:23"},"0.1.23":{"tag":"0.1.23","author":"aristech","date":"2026-06-03 09:29:07"}},"upgrade_notice":{"0.1.23":"<p>Confirmed compatible with WordPress 7.0. Documentation-only release \u2014 no source-code changes.<\/p>","0.1.22":"<p>Version bump only \u2014 no source-code changes vs 0.1.21. Re-submission for WordPress.org plugin directory review.<\/p>","0.1.21":"<p>Better compatibility with security plugins that move the login URL: the plugin now uses wp_login_url() instead of hardcoded \/wp-login.php references.<\/p>","0.1.20":"<p>WordPress.org plugin review compliance: unminified TypeScript source for the bundled web-components library is now vendored alongside the compiled bundle. No runtime behaviour change.<\/p>","0.1.19":"<p>Recommended. Removes the <code>qrauth_psl_provisioning_role<\/code> filter \u2014 auto-provisioned users are now hardcoded to Subscriber with no code path to elevate. Operators needing a different role for a user must change it manually via Users \u2192 All Users after first sign-in. WordPress.org review fix.<\/p>","0.1.18":"<p>Recommended. The <code>\/verify<\/code> route now fires <code>wp_login<\/code> on success and <code>wp_login_failed<\/code> on signature mismatch so security plugins (Limit Login Attempts, Wordfence), audit logs, MFA gates, and last-login trackers see QRAuth sign-ins as if they came through wp-login.php. Also adds an <code>== External resources ==<\/code> readme section, switches <code>Plugin URI:<\/code> to the plugin&#039;s GitHub repo, and adds a top-level GPL-2.0 LICENSE file. No configuration changes required.<\/p>","0.1.17":"<p>Mobile sign-in robustness: the &quot;Continue with QRAuth&quot; flow now reliably returns users to your site after approval, including on sites that send a strict Referrer-Policy header. No configuration change required.<\/p>","0.1.16":"<p>WordPress.org review fixes. Vendored web component bumped to 0.4.1 (no more api.qrserver.com fetch). External services section added to readme. Auto-provision role UI is now Subscriber-only \u2014 sites that need Contributor\/Author must use the new <code>qrauth_psl_provisioning_role<\/code> filter.<\/p>","0.1.14":"<p>Cosmetic readme fix \u2014 <code>Contributors:<\/code> now points at the actual wordpress.org profile. No runtime changes.<\/p>","0.1.13":"<p>Security hardening \u2014 Tenant URL sanitiser now rejects plain-http localhost values unless <code>WP_DEBUG<\/code> is on or the <code>qrauth_psl_allow_localhost_tenant_url<\/code> filter opts in. Blocks an admin-gated SSRF probe path. No action required for sites using <code>https:\/\/<\/code> tenants.<\/p>","0.1.12":"<p>CI \/ release-infrastructure bump only \u2014 runners now use Node.js 24. Zero runtime behaviour changes.<\/p>","0.1.11":"<p>Plugin-check hygiene before WordPress.org submission: composer.json now shipped alongside vendor\/, discouraged <code>load_plugin_textdomain()<\/code> call removed, 0.1.8 upgrade notice trimmed under the 300-char cap. No runtime behaviour changes.<\/p>","0.1.10":"<p>Translation scaffolding only \u2014 no behaviour changes. New UI strings from 0.1.1 \u2192 0.1.9 are now picked up by the POT + el_GR.po so translators have a current baseline to work from.<\/p>","0.1.9":"<p>Customers signing in through a WooCommerce form now land on My Account (or the checkout step they started on) instead of wp-admin. No configuration change required.<\/p>","0.1.8":"<p>Mobile \/ multilingual fixes: a phone scanning a QR from another device no longer signs itself in; the redirect URL is language-neutral so WPML \/ Polylang \/ Weglot sites need only one allowlist entry.<\/p>","0.1.7":"<p>Adds WooCommerce support \u2014 the widget can now appear on the My Account login\/register forms and on the checkout sign-in step. Opt in per-surface under Settings \u2192 QRAuth.<\/p>","0.1.6":"<p>Adds a <code>[qrauth_login]<\/code> shortcode so the widget can live on any page, not just wp-login.php. Opt in from Settings \u2192 QRAuth. No existing configuration changes on upgrade.<\/p>","0.1.5":"<p>Adds mobile same-device sign-in. One-time setup: register your <code>\/wp-login.php<\/code> URL in your QRAuth app&#039;s redirect-URL allowlist (Settings \u2192 QRAuth shows the exact URL). Desktop sign-in is unaffected either way.<\/p>","0.1.4":"<p>Required hotfix. 0.1.3 accepted QRAuth signatures but rejected every login with <code>provision_disabled<\/code> because the widget was requesting only the <code>identity<\/code> scope (the scope attribute name was wrong). Upgrade to restore end-to-end sign-in.<\/p>","0.1.3":"<p>Required hotfix. 0.1.2 still failed to accept real QRAuth signatures because the envelope separator <code>:<\/code> wasn&#039;t in the allowed alphabet. Upgrade to restore end-to-end sign-in.<\/p>","0.1.2":"<p>Recommended hotfix. Unblocks end-to-end QRAuth sign-in on sites running 0.1.1 \u2014 the <code>\/verify<\/code> route&#039;s input validators were too strict and rejected every real login. No configuration change required.<\/p>","0.1.1":"<p>Adds a same-origin REST proxy that eliminates browser CORS errors. Upgrading requires adding a Client Secret in Settings \u2192 QRAuth; the existing Client ID and the renamed Tenant URL (previously &quot;Base URL&quot;) are migrated automatically.<\/p>","0.1.0":"<p>Initial public release.<\/p>"},"ratings":{"1":0,"2":0,"3":0,"4":0,"5":1},"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3559096,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3559096,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3559096,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3559096,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3559096,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.1.21","0.1.23"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3559096,"resolution":"1","location":"assets","locale":"","width":1280,"height":800},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3559096,"resolution":"2","location":"assets","locale":"","width":1280,"height":800},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3559096,"resolution":"3","location":"assets","locale":"","width":1280,"height":800},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3559096,"resolution":"4","location":"assets","locale":"","width":1280,"height":800}},"screenshots":{"1":"wp-login.php with the \"Sign in with QRAuth\" button under the password field.","2":"Widget active \u2014 animated QR, session countdown, and \"Continue on this device\" CTA for mobile users.","3":"Settings \u2192 QRAuth \u2014 Client ID, Client Secret, Tenant URL, auto-provision toggle, default role, allowed scopes, and per-surface enable switches.","4":"WooCommerce registration form \u2014 inline widget alongside WC's account-creation fields."}},"plugin_section":[],"plugin_tags":[710,602,9223,1373,2056],"plugin_category":[38],"plugin_contributors":[265379],"plugin_business_model":[],"class_list":["post-304732","plugin","type-plugin","status-publish","hentry","plugin_tags-authentication","plugin_tags-login","plugin_tags-passwordless","plugin_tags-qr-code","plugin_tags-social-login","plugin_category-authentication","plugin_contributors-aristech","plugin_committers-aristech"],"banners":{"banner":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/banner-772x250.png?rev=3559096","banner_2x":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/banner-1544x500.png?rev=3559096","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/icon.svg?rev=3559096","icon":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/icon.svg?rev=3559096","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/screenshot-1.png?rev=3559096","caption":"wp-login.php with the \"Sign in with QRAuth\" button under the password field."},{"src":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/screenshot-2.png?rev=3559096","caption":"Widget active \u2014 animated QR, session countdown, and \"Continue on this device\" CTA for mobile users."},{"src":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/screenshot-3.png?rev=3559096","caption":"Settings \u2192 QRAuth \u2014 Client ID, Client Secret, Tenant URL, auto-provision toggle, default role, allowed scopes, and per-surface enable switches."},{"src":"https:\/\/ps.w.org\/qrauth-passwordless-social-login\/assets\/screenshot-4.png?rev=3559096","caption":"WooCommerce registration form \u2014 inline widget alongside WC's account-creation fields."}],"raw_content":"<!--section=description-->\n<p>QRAuth replaces the password field on your WordPress login page with a drop-in QR widget. Users sign in by scanning with the QRAuth mobile app; a cryptographic signature is verified server-to-server before WordPress sets the auth cookie. Social login (Google, GitHub, Microsoft, Apple) is brokered by QRAuth's hosted approval page, so you never have to register an OAuth app or hold a client secret.<\/p>\n\n<p><strong>One Client ID is the only configuration.<\/strong> Paste it into Settings \u2192 QRAuth and the widget appears on wp-login.php. Everything else \u2014 the approval flow, the signing, the token refresh \u2014 lives in the QRAuth platform.<\/p>\n\n<p><strong>Account safety is the default.<\/strong> Auto-provisioning is off out of the box: only WordPress users who already exist (matched on email) can sign in via QRAuth. Flip on auto-provisioning and new users are created as Subscriber \u2014 that's the only role available, intentionally and at every layer (settings UI, sanitiser, runtime). Operators who need a different role for an individual user can change it manually via Users \u2192 All Users after their first sign-in. The plugin never stores the signing material, never issues a redirect outside your site, and never touches your user table on uninstall.<\/p>\n\n<p><strong>Self-hosted, no third-party scripts on wp-login.php.<\/strong> The QRAuth web component ships vendored inside the plugin \u2014 the only outbound call is from your server to QRAuth's verification endpoint during a sign-in attempt.<\/p>\n\n<p><strong>Open source build.<\/strong> The compressed JavaScript at <code>assets\/js\/qrauth-components.js<\/code> is built from publicly available TypeScript source at https:\/\/github.com\/qrauth-io\/qrauth\/tree\/main\/packages\/web-components. The unminified source files are also vendored alongside the minified bundle inside this plugin (<code>assets\/js\/source\/<\/code>) for offline review. See the <strong>Source<\/strong> section below for build instructions.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin connects to QRAuth (https:\/\/qrauth.io) \u2014 the identity verification service that performs the actual passwordless \/ social sign-in. QRAuth is operated by ProgressNet, the publisher of this plugin. Without QRAuth there is no widget and no sign-in.<\/p>\n\n<p><strong>What the service is and what it is used for<\/strong><\/p>\n\n<p>QRAuth verifies that the user who scanned the QR code (or completed a social-provider flow on the hosted approval page) is the same person who initiated the sign-in on your WordPress site, then returns a signed assertion that the plugin uses to set the WordPress auth cookie.<\/p>\n\n<p><strong>What data is sent and when<\/strong><\/p>\n\n<ul>\n<li><strong>Auth-session creation<\/strong> \u2014 when a visitor opens a page that hosts the widget (wp-login.php, the registration form, a shortcode-enabled page, or a WooCommerce sign-in form), the plugin's same-origin REST proxy sends your Client ID, Client Secret (server-side only \u2014 never exposed to the browser), and the host page URL to <code>https:\/\/qrauth.io\/api\/v1\/auth-sessions<\/code>. No visitor data is included in this request.<\/li>\n<li><strong>Sign-in verification<\/strong> \u2014 when the visitor approves the sign-in (by scanning with the QRAuth mobile app or completing a social-provider flow on QRAuth's hosted approval page), the plugin's REST proxy fetches the verified result from <code>https:\/\/qrauth.io\/api\/v1\/auth-sessions\/verify-result<\/code>. The response carries the QRAuth user identifier and, when the <code>email<\/code> scope is allowed in Settings \u2192 QRAuth, the user's email address. The plugin uses this only to locate or create the matching WordPress user; nothing beyond a hashed link reference is retained.<\/li>\n<li><strong>Hosted approval page<\/strong> \u2014 when a visitor on a phone taps \"Continue with QRAuth\", the browser navigates to <code>https:\/\/qrauth.io\/a\/&lt;token&gt;<\/code> to complete the social-provider flow. This is a standard cross-domain navigation initiated by the visitor.<\/li>\n<\/ul>\n\n<p>The vendored web component (<code>assets\/js\/qrauth-components.js<\/code>) is served from your own WordPress site \u2014 there is no third-party JavaScript on wp-login.php, and the component does not contact qrauth.io directly from the browser; all server-to-server calls are proxied via your site's REST API.<\/p>\n\n<p><strong>Service terms and policies<\/strong><\/p>\n\n<ul>\n<li>Terms of Service: https:\/\/qrauth.io\/terms<\/li>\n<li>Privacy Policy: https:\/\/qrauth.io\/privacy<\/li>\n<li>Data Processing Addendum: https:\/\/qrauth.io\/dpa<\/li>\n<li>List of Sub-processors: https:\/\/qrauth.io\/subprocessors<\/li>\n<\/ul>\n\n<h3>Source<\/h3>\n\n<!-- Keep the banner code block below in sync with the first lines of assets\/js\/qrauth-components.js after every web-components version bump. -->\n\n<p>The compiled bundle at <code>assets\/js\/qrauth-components.js<\/code> carries the following banner header at the top of the file:<\/p>\n\n<pre><code>`\n<\/code><\/pre>\n\n<p>\/*!\n * @qrauth\/web-components v0.4.1\n * Vendored by qrauth-passwordless-social-login. Do not edit by hand.\n *\n * Source:  https:\/\/github.com\/qrauth-io\/qrauth\/tree\/main\/packages\/web-components\n * License: MIT\n * npm:     https:\/\/www.npmjs.com\/package\/@qrauth\/web-components\n * Build:   <code>npm install &amp;&amp; npm run build:assets<\/code> (see bin\/fetch-web-components.mjs)\n *\n * The unminified TypeScript source for this bundle is also vendored at\n * assets\/js\/source\/ \u2014 see assets\/js\/source\/README.md for provenance.\n *\/\n    `<\/p>\n\n<p>The unminified TypeScript source files are also vendored alongside the compiled bundle inside this plugin (<code>assets\/js\/source\/<\/code>) for offline review.<\/p>\n\n<p>The plugin's own source \u2014 PHP, the small browser adapter (<code>assets\/js\/qrauth-adapter.js<\/code>), build scripts, tests, and CI \u2014 is publicly maintained under GPL-2.0-or-later at:<\/p>\n\n<ul>\n<li>Plugin source repository: https:\/\/github.com\/qrauth-io\/qrauth-passwordless-social-login<\/li>\n<\/ul>\n\n<p>The PHP and <code>assets\/js\/qrauth-adapter.js<\/code> shipped in the plugin ZIP are non-minified \u2014 read them directly without checking out the repo.<\/p>\n\n<p>The vendored file <code>assets\/js\/qrauth-components.js<\/code> is a pinned production build of the public <code>@qrauth\/web-components<\/code> library. The non-compiled source for that library is openly available:<\/p>\n\n<ul>\n<li>Source repository: https:\/\/github.com\/qrauth-io\/qrauth\/tree\/main\/packages\/web-components (MIT-licensed)<\/li>\n<li>npm release: https:\/\/www.npmjs.com\/package\/@qrauth\/web-components \u2014 pinned to v0.4.1, sha512 in <code>package.json<\/code> under the <code>qrauth.webComponentsIntegrity<\/code> key<\/li>\n<li>Build instructions for the upstream library: https:\/\/github.com\/qrauth-io\/qrauth\/blob\/main\/BUILDING.md<\/li>\n<\/ul>\n\n<p>To regenerate the vendored bundle from the pinned npm release, clone the plugin source repository linked above and run from its root:<\/p>\n\n<pre><code>npm install\nnpm run build:assets\n<\/code><\/pre>\n\n<p>The build script <code>bin\/fetch-web-components.mjs<\/code> (kept in the plugin source repository, not in the WordPress.org plugin ZIP) downloads the npm tarball, verifies its sha512 SRI hash against <code>package.json#qrauth.webComponentsIntegrity<\/code>, extracts the IIFE build, and writes it to <code>assets\/js\/qrauth-components.js<\/code>. CI runs the same script before the WordPress.org plugin-check job, so the bundle distributed on the directory always matches the published npm release. To rebuild from upstream source instead of the pinned tarball, follow <code>BUILDING.md<\/code> in the upstream library repository above.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin or install via WP-Admin \u2192 Plugins \u2192 Add New.<\/li>\n<li>Activate through the 'Plugins' menu in WordPress.<\/li>\n<li>Go to Settings \u2192 QRAuth and paste your Client ID and Client Secret from https:\/\/qrauth.io\/dashboard. The Client Secret is used server-side only \u2014 it never reaches the browser.<\/li>\n<li>In the QRAuth dashboard (<strong>Apps \u2192 your app \u2192 Redirect URLs<\/strong>), register your site's login URL \u2014 typically <code>https:\/\/&lt;your-site&gt;\/wp-login.php<\/code>. Without this registration, sign-in on phones (same-device approval) cannot complete; desktop sign-in works without it. The exact URL to register is shown on the Settings \u2192 QRAuth page. One registration is enough even on multilingual sites (WPML, Polylang, Weglot): the plugin uses the language-neutral admin URL, not the translated home URL, so <code>\/en\/wp-login.php<\/code> \/ <code>\/fr\/wp-login.php<\/code> \/ etc. don't need separate entries.<\/li>\n<li>Log out and try signing in via the \"Sign in with QRAuth\" button on wp-login.php.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"do%20my%20users%20need%20a%20qrauth%20account%3F\"><h3>Do my users need a QRAuth account?<\/h3><\/dt>\n<dd><p>Only if they sign in via QR. They install the free QRAuth mobile app once, scan the code on your login page, and approve on their phone. For social login (Google, GitHub, Microsoft, Apple) the user simply taps the provider on QRAuth's hosted approval page \u2014 they don't need a QRAuth account at all.<\/p><\/dd>\n<dt id=\"where%20are%20passwords%20stored%3F\"><h3>Where are passwords stored?<\/h3><\/dt>\n<dd><p>They aren't. Auto-provisioned accounts get a 32-character random password that nobody (including you) holds or needs. WordPress users who already had a password keep it \u2014 QRAuth just becomes an additional sign-in method alongside the usual form.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20existing%20wordpress%20users%3F\"><h3>Does it work with existing WordPress users?<\/h3><\/dt>\n<dd><p>Yes. If a scanned account's email matches an existing WordPress user, they log into that account \u2014 no duplicate is created. The match is remembered via user meta, so subsequent sign-ins skip the email lookup.<\/p><\/dd>\n<dt id=\"what%20role%20do%20new%20users%20get%3F\"><h3>What role do new users get?<\/h3><\/dt>\n<dd><p>Subscriber, hardcoded. The settings UI only offers Subscriber, the sanitiser clamps any other value to Subscriber, and the provisioner ignores the stored option entirely and uses Subscriber unconditionally \u2014 this aligns with WordPress.org plugin directory guidelines for sign-in plugins that create users post-external-verification. There is no plugin-provided code path (filter, action, option, or constant) to elevate the auto-provisioning role. If a particular user needs a higher role, change it manually via Users \u2192 All Users after their first sign-in.<\/p><\/dd>\n<dt id=\"can%20i%20unlink%20a%20wordpress%20account%20from%20qrauth%3F\"><h3>Can I unlink a WordPress account from QRAuth?<\/h3><\/dt>\n<dd><p>Yes \u2014 from Users \u2192 Your Profile there's a QRAuth section with an \"Unlink QRAuth\" button. Admins can unlink other users from their profiles too. The WordPress account (role, posts, comments, history) is preserved; only the link metadata is removed.<\/p><\/dd>\n<dt id=\"can%20i%20remove%20the%20plugin%20later%3F\"><h3>Can I remove the plugin later?<\/h3><\/dt>\n<dd><p>Yes. Deactivate and all your users stay. Uninstall removes the plugin's settings row and its rate-limit transients \u2014 it never deletes WordPress user accounts or their content. If you reinstall later, existing users re-link automatically on their next QRAuth sign-in.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20phone%20home%20with%20analytics%3F\"><h3>Does the plugin phone home with analytics?<\/h3><\/dt>\n<dd><p>No. There's no telemetry, no analytics, no third-party scripts. The only outbound request is from your server to <code>https:\/\/qrauth.io\/api\/v1\/auth-sessions\/verify-result<\/code> during a login attempt, carrying only the session ID and the signature the user's phone produced.<\/p><\/dd>\n<dt id=\"how%20is%20rate%20limiting%20handled%3F\"><h3>How is rate limiting handled?<\/h3><\/dt>\n<dd><p>The REST verify route caps requests at 10 per 5 minutes per hashed IP. The hash uses <code>wp_salt()<\/code>, so a database dump can't recover caller IPs. Each verify attempt costs one outbound call to QRAuth's servers, so tight limits protect both your site and ours.<\/p><\/dd>\n<dt id=\"does%20it%20work%20on%20multisite%3F\"><h3>Does it work on multisite?<\/h3><\/dt>\n<dd><p>Per-site activation works today. Network-activated multisite is tracked for a future release.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.1.23<\/h4>\n\n<ul>\n<li>Compatibility: confirmed working on WordPress 7.0 and bumped \"Tested up to\" accordingly. Verified on a clean 7.0 install \u2014 plugin activation, settings option, and the login-page widget injection all run without fatals or deprecation notices. No source-code changes.<\/li>\n<\/ul>\n\n<h4>0.1.22<\/h4>\n\n<ul>\n<li>Re-submission for WordPress.org plugin directory review. No source-code changes vs 0.1.21 \u2014 only the version metadata is bumped. The round-4 review comments cited line numbers that pre-dated 0.1.21's <code>wp_login_url()<\/code> migration, so this bump exists only to ensure the reviewer's tooling re-scans the post-fix tree.<\/li>\n<\/ul>\n\n<h4>0.1.21<\/h4>\n\n<ul>\n<li>Use <code>wp_login_url()<\/code> instead of hardcoded <code>site_url('\/wp-login.php')<\/code> for the two locations that reference the W login URL. This respects security plugins that move wp-login.php to a custom URL and is the recommended pattern per the WordPress developer handbook.<\/li>\n<\/ul>\n\n<h4>0.1.20<\/h4>\n\n<ul>\n<li>Compliance: vendored the TypeScript source for the bundled <code>@qrauth\/web-components<\/code> library at <code>assets\/js\/source\/<\/code> alongside the minified bundle, so reviewers and integrators can read the unminified source without leaving the plugin. The readme's <code>== Source ==<\/code> section (renamed from <code>== External resources ==<\/code> for clarity) and the Description's \"Open source build\" paragraph also link to the canonical upstream repository and build instructions. WordPress.org plugin guideline 4 (human-readable code).<\/li>\n<\/ul>\n\n<h4>0.1.19<\/h4>\n\n<ul>\n<li>Account safety: the <code>qrauth_psl_provisioning_role<\/code> filter introduced in 0.1.16 has been removed. Auto-provisioned users are now hardcoded to the <code>subscriber<\/code> role at every layer \u2014 settings UI, sanitiser, and runtime provisioner \u2014 with no programmatic path (filter, action, option, or constant) to elevate. Operators who need a different role for an individual user must update it manually via Users \u2192 All Users after first sign-in. Identified during WordPress.org plugin directory review (creating users post-external-verification must be capped at the lowest privilege role).<\/li>\n<\/ul>\n\n<h4>0.1.18<\/h4>\n\n<ul>\n<li>Security ecosystem compatibility: the <code>\/verify<\/code> route now fires the canonical <code>wp_login<\/code> action on every successful sign-in (mirroring what core's <code>wp_signon()<\/code> does after <code>wp_set_auth_cookie()<\/code>) and <code>wp_login_failed<\/code> on <code>signature_invalid<\/code> rejections. Security plugins (Limit Login Attempts Reloaded, Wordfence, Solid Security), audit-log plugins (WP Activity Log, Simple History), MFA gates, and \"last login\" \/ session-tracking plugins now see QRAuth sign-ins exactly as if the user had authenticated through <code>wp-login.php<\/code>. Identified during WordPress.org plugin directory review \u2014 closes the \"creating your own login method can bypass security plugins\" concern raised in the review. No configuration changes required.<\/li>\n<li>Documentation: added an <code>== External resources ==<\/code> section to the readme that publishes the plugin's own GitHub repository (https:\/\/github.com\/qrauth-io\/qrauth-passwordless-social-login, GPL-2.0-or-later) and points at the public source for the vendored <code>@qrauth\/web-components<\/code> library (https:\/\/github.com\/qrauth-io\/qrauth\/tree\/main\/packages\/web-components, MIT) along with the npm release the bundle is pinned to and the <code>npm run build:assets<\/code> step that reproduces it. The vendored file's banner now names its source repository, license, npm release, and build entry-point inline. The plugin header's <code>Plugin URI:<\/code> now points at the GitHub repository instead of the QRAuth platform homepage, and a top-level <code>LICENSE<\/code> file (GPL-2.0) has been added to the repository so forkers and reviewers see the licence at a glance. Addresses WordPress.org plugin directory feedback that compiled JS must link to a publicly maintained, human-readable source.<\/li>\n<\/ul>\n\n<h4>0.1.17<\/h4>\n\n<ul>\n<li>Fixed: same-device mobile sign-in's \"Continue with QRAuth\" URL no longer drops its query string when proxying to the QRAuth tenant. Without this, the signal that distinguishes same-device clicks from cross-device QR scans was being stripped at the WordPress proxy layer, so the hosted approval page could no longer decide reliably whether to redirect after approval \u2014 sites with a strict <code>Referrer-Policy<\/code> could end up landing the user on a \"you can close this page\" terminal state instead of returning them to wp-login.php. The <code>\/a\/&lt;token&gt;<\/code> proxy now forwards the query string verbatim, matching the existing behaviour documented for the <code>\/api\/v1\/auth-sessions\/&lt;id&gt;<\/code> proxy.<\/li>\n<\/ul>\n\n<h4>0.1.16<\/h4>\n\n<ul>\n<li>Security: bumped vendored QRAuth web component to 0.4.1, which renders QR codes locally instead of fetching them from a third-party image service (api.qrserver.com). No more outbound calls leave the visitor's browser to hosts other than your own site. Identified during WordPress.org plugin review.<\/li>\n<li>Privacy: the readme now carries an explicit <code>== External services ==<\/code> section documenting every outbound call the plugin makes, with links to the QRAuth Terms, Privacy Policy, DPA, and Sub-processors list.<\/li>\n<li>Account safety: the auto-provisioning UI now offers only \"Subscriber\" \u2014 Contributor and Author have moved behind the new <code>qrauth_psl_provisioning_role<\/code> filter, accessible only via code. Editor and Administrator remain hard-blocked at every layer, as before. Existing sites configured with Contributor or Author will fall back to Subscriber on the next save; until then their stored value is unchanged.<\/li>\n<\/ul>\n\n<h4>0.1.15<\/h4>\n\n<ul>\n<li>Author URI: https:\/\/github.com\/aristech<\/li>\n<\/ul>\n\n<h4>0.1.14<\/h4>\n\n<ul>\n<li>Readme: <code>Contributors:<\/code> field set to <code>aristech<\/code> \u2014 the verified wordpress.org account that owns the submission. The prior value (<code>qrauth<\/code>) wasn't a registered wordpress.org profile and would have rendered as a broken contributor badge on the plugin directory page.<\/li>\n<\/ul>\n\n<h4>0.1.13<\/h4>\n\n<ul>\n<li>Security (low-severity hardening): the Tenant URL sanitiser now rejects <code>http:\/\/localhost<\/code> and <code>http:\/\/127.0.0.1<\/code> values on production sites \u2014 they're only accepted when <code>WP_DEBUG<\/code> is on (local development) or when a site operator explicitly opts in via the new <code>qrauth_psl_allow_localhost_tenant_url<\/code> filter. Closes a pentest finding: an admin with <code>manage_options<\/code> could previously point Tenant URL at arbitrary localhost ports (e.g. MySQL, Redis), using the plugin's outbound <code>wp_remote_request<\/code> as a port-scanning oracle against the WP host's internal network. Now admin-gated SSRF via this path is blocked by default; <code>https:\/\/<\/code> tenants remain accepted unchanged.<\/li>\n<\/ul>\n\n<h4>0.1.12<\/h4>\n\n<ul>\n<li>CI\/release infrastructure: bumped all GitHub Actions flagged by the Node-20 deprecation warning (<code>actions\/checkout<\/code>, <code>actions\/setup-node<\/code>, <code>softprops\/action-gh-release<\/code>, <code>actions\/upload-artifact<\/code>, <code>ramsey\/composer-install<\/code>) to current major versions so every runner step uses Node.js 24. No plugin behaviour changes \u2014 only the CI environment that builds + releases the plugin changed.<\/li>\n<\/ul>\n\n<h4>0.1.11<\/h4>\n\n<ul>\n<li>Fixed: three plugin-check warnings surfaced while preparing the WordPress.org submission. (1) The release ZIP now includes <code>composer.json<\/code> alongside <code>vendor\/<\/code> so reviewers can see the provenance of the vendored dependencies. (2) Removed the explicit <code>load_plugin_textdomain()<\/code> call from plugin bootstrap \u2014 WordPress 4.6+ auto-loads translations for plugins hosted on wordpress.org, and an explicit call is now discouraged. (3) Trimmed the 0.1.8 upgrade notice to stay under WordPress.org's 300-character limit.<\/li>\n<\/ul>\n\n<h4>0.1.10<\/h4>\n\n<ul>\n<li>i18n: regenerated the translation template (<code>languages\/qrauth-passwordless-social-login.pot<\/code>) and the Greek scaffold (<code>el_GR.po<\/code>) so every user-facing string added between 0.1.1 and 0.1.9 (Client Secret field, Tenant URL description, per-surface enable switches for shortcode and WooCommerce, mobile sign-in setup callout, etc.) is now available to translators. No behaviour changes; string extraction only.<\/li>\n<\/ul>\n\n<h4>0.1.9<\/h4>\n\n<ul>\n<li>Changed: after a successful sign-in, customers on WooCommerce surfaces now land on their My Account page (or the checkout step, if that's where they started) instead of wp-admin. Sign-ins from wp-login.php still go to wp-admin. Sign-ins from a custom page (shortcode) go back to that page. Decided server-side from the request's Referer, validated same-origin via WordPress core's <code>wp_validate_redirect<\/code> so a forged Referer can't turn this into an open redirect.<\/li>\n<\/ul>\n\n<h4>0.1.8<\/h4>\n\n<ul>\n<li>Fixed: cross-device QR scan no longer inadvertently signs in the scanning device. Previously, a phone that scanned a desktop-initiated QR and approved on qrauth.io would get redirected back to wp-login.php and auto-sign-in, because the landing-page adapter couldn't distinguish \"this browser initiated the session\" from \"this browser just happens to be visiting the redirect URL\". The proxy now stamps a short-lived, browser-scoped cookie at session-create time, and the adapter only auto-completes sign-in when that cookie matches the sessionId in the URL \u2014 same-device mobile flow unaffected, cross-device leaves the scanning device on the login page.<\/li>\n<li>Fixed: multilingual sites using WPML \/ Polylang \/ Weglot no longer need a separate redirect-URL registered per language. The widget now emits its <code>redirect-uri<\/code> using WordPress's language-neutral admin URL (<code>site_url<\/code>) instead of the translated home URL, so <code>\/en\/wp-login.php<\/code>, <code>\/fr\/wp-login.php<\/code>, etc. all resolve to the same canonical registration in the QRAuth dashboard.<\/li>\n<\/ul>\n\n<h4>0.1.7<\/h4>\n\n<ul>\n<li>Added: WooCommerce integration. When WooCommerce is active, Settings \u2192 QRAuth shows two new checkboxes \u2014 \"WooCommerce My Account + checkout login\" (covers \/my-account\/ and the checkout page's returning-customer sign-in) and \"WooCommerce registration form\". The widget appears inside WooCommerce's own forms via the standard <code>woocommerce_login_form_end<\/code> \/ <code>woocommerce_register_form_end<\/code> template hooks.<\/li>\n<li>Settings checkboxes are hidden on non-WooCommerce sites \u2014 the plugin stays clean on simple blogs.<\/li>\n<\/ul>\n\n<h4>0.1.6<\/h4>\n\n<ul>\n<li>Added: <code>[qrauth_login]<\/code> shortcode for placing the widget on any page, post, or widget area. Supports <code>display=\"inline|button\"<\/code> (default <code>inline<\/code>) and <code>mode=\"login|register\"<\/code> (default <code>login<\/code>). Opt in via Settings \u2192 QRAuth \u2192 \"Show widget on\" \u2192 \"Anywhere via shortcode\".<\/li>\n<li>Widget asset loading now runs on front-end pages when the shortcode is used, not just on <code>wp-login.php<\/code>. The ~65 KB bundle is only emitted on pages that actually render the widget \u2014 unused shortcode-enabled sites pay zero bandwidth cost on pages without the tag.<\/li>\n<\/ul>\n\n<h4>0.1.5<\/h4>\n\n<ul>\n<li>Added: mobile same-device sign-in. The login widget now emits <code>redirect-uri<\/code> pointing at <code>wp-login.php<\/code>, and the adapter picks up the <code>qrauth_session_id<\/code> \/ <code>qrauth_signature<\/code> query params that qrauth.io appends when the user approves on their phone. The WP tab being suspended while the user was on qrauth.io no longer breaks the flow.<\/li>\n<li>Added: Settings \u2192 QRAuth now shows the exact URL admins need to register in their QRAuth app's redirect-URL allowlist (typically <code>https:\/\/&lt;site&gt;\/wp-login.php<\/code>). Required one-time setup for phone sign-in; desktop still works without it.<\/li>\n<\/ul>\n\n<h4>0.1.4<\/h4>\n\n<ul>\n<li>Fixed: logins completed past <code>\/verify<\/code> on 0.1.3 but bounced with <code>provision_disabled<\/code> \u2014 regardless of whether auto-provisioning was on. The widget's scope attribute was emitted as <code>scope<\/code> (singular) while the QRAuth web component reads <code>scopes<\/code> (plural), so only the default <code>identity<\/code> scope was requested and <code>\/verify-result<\/code> returned no email. Email-based account matching and new-user provisioning both rely on having the email, so both paths failed with the same error code. Restored the correct attribute name \u2014 no action required on upgrade.<\/li>\n<\/ul>\n\n<h4>0.1.3<\/h4>\n\n<ul>\n<li>Fixed: <code>\/verify<\/code> still rejected real logins on 0.1.2 with <code>rest_invalid_param<\/code> because the signature value QRAuth returns is an envelope (<code>&lt;keyId&gt;:&lt;base64sig&gt;<\/code>), and the validator's alphabet didn't include the <code>:<\/code> separator. Added <code>:<\/code> and <code>.<\/code> to the accepted character set. No action required on upgrade.<\/li>\n<\/ul>\n\n<h4>0.1.2<\/h4>\n\n<ul>\n<li>Fixed: <code>\/verify<\/code> rejected every real login with <code>rest_invalid_param<\/code> because the input validators were pinned to old formats. <code>sessionId<\/code> now accepts QRAuth's current cuid format (not only UUIDs); <code>signature<\/code> now accepts standard base64 (with <code>+<\/code>, <code>\/<\/code>, <code>=<\/code>) in addition to base64url. No action required on upgrade \u2014 auth on existing sites starts working again automatically.<\/li>\n<\/ul>\n\n<h4>0.1.1<\/h4>\n\n<ul>\n<li>Added a same-origin REST proxy (<code>qrauth-psl\/v1\/api\/v1\/auth-sessions<\/code>, <code>\u2026\/a\/&lt;token&gt;<\/code>) so the login widget talks to your site's own WordPress REST API. No more browser-level CORS errors on third-party hosts.<\/li>\n<li>New <strong>Client Secret<\/strong> setting. Used server-side only to authenticate the proxy against qrauth.io. Never exposed to the browser.<\/li>\n<li>Options rename: <code>base_url<\/code> \u2192 <code>tenant_url<\/code>. Existing configurations are migrated automatically on upgrade; no manual action required.<\/li>\n<li>Tenant URL help text clarified for non-technical admins; a debug line showing the effective REST URL is now emitted only when <code>WP_DEBUG<\/code> is on.<\/li>\n<li>Dropped the widget's <code>redirect-uri<\/code> attribute \u2014 it was never used by the plugin and triggered exact-match allowlist rejections on qrauth.io.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Initial public release.<\/li>\n<li>Passwordless sign-in on wp-login.php via the vendored QRAuth web component (v0.4.0).<\/li>\n<li>Server-side cryptographic verification against QRAuth's <code>\/verify-result<\/code> endpoint with TLS + 10 s timeout.<\/li>\n<li>Auto-provisioning (off by default) with role clamped to subscriber \/ contributor \/ author.<\/li>\n<li>Per-user profile UI for unlinking QRAuth, preserving the WordPress account.<\/li>\n<li>Uninstall cleanup that removes plugin options + transients but never touches <code>wp_users<\/code> or <code>wp_usermeta<\/code>.<\/li>\n<li>REST route rate-limited at 10 requests per 5 minutes per hashed IP.<\/li>\n<li>Full i18n scaffolding (POT + Greek translation source).<\/li>\n<\/ul>","raw_excerpt":"Passwordless sign-in for WordPress. Users scan a QR code with the QRAuth mobile app \u2014 no passwords, no forms, no OAuth apps to register.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/304732","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=304732"}],"author":[{"embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/aristech"}],"wp:attachment":[{"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=304732"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=304732"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=304732"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=304732"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=304732"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/fa.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=304732"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}