Skip to content

feat(auth): draw the two-factor QR code on the server - #183

Merged
Panelica merged 4 commits into
Panelica:mainfrom
ertugrulkeyvan:fix/2fa-qr-local
Oct 12, 2026
Merged

Panelica merged 4 commits into
Panelica:mainfrom
ertugrulkeyvan:fix/2fa-qr-local

Conversation

@ertugrulkeyvan

Copy link
Copy Markdown
Contributor

What

The two-factor setup screens draw their QR code on the server instead of loading it from a public QR service.

Both setup screens, the staff one (admin/auth/enable-two-factor) and the customer one (client/auth/enable-two-factor), loaded the code from api.qrserver.com, with the otpauth:// setup address in the request:

<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={{ urlencode($qrUrl) }}">

After this:

  • Drawn here: the code is drawn on the server and embedded in the page as an SVG data URI.
  • No outside service: the screens no longer depend on it being up and reachable, and the setup address, which carries the shared secret, stays on the server.
  • Same look: the code is the same size, and the "enter it manually" fallback is unchanged.
  • No new dependency: bacon/bacon-qr-code is already required, and the invoice PDF already uses it for its payment QR. The SVG back end needs no PHP extension.

A note for the release notes, if you think it fits: accounts that set up two-factor sign-in through the old screens sent their setup address to that service. Turning two-factor off and on again gives them a new secret.

How

  • App\Support\QrCode::dataUri(): draws the code with bacon-qr-code's ImageRenderer and SvgImageBackEnd and returns a data:image/svg+xml;base64, URI.
  • Controllers: Admin\AuthController::enable2fa() and Client\AuthController::enable2fa() pass it to the view as $qrImage. $qrUrl is still passed, so a theme that overrides the view keeps working until it switches over.
  • Views: both use $qrImage.

The work is split into four commits: the helper, the staff screen, the customer screen and the tests.

Tests

tests/Feature/TwoFactorQrLocalTest.php, 3 tests:

  • Staff screen: no qrserver.com, an inline SVG image, and no otpauth address anywhere in the page.
  • Customer screen: the same.
  • Helper: returns an SVG data URI of the right size, with nothing readable of the secret in it.

They fail on main. AdminTwoFactorReachableTest and TwoFactorBackupCodesShownTest pass unchanged.

Full suite: 4089 passed. The one failure is the CurrencyAndBackupTest db backup test, which fails on main too in my local setup.

@Panelica
Panelica merged commit d87ffc2 into Panelica:main Oct 12, 2026
1 check passed
@Panelica

Copy link
Copy Markdown
Owner

Thank you, @ertugrulkeyvan, merged. Sending the otpauth address, and the shared secret in it, to a public QR service was a real leak, and drawing the code on the server closes it. We read the generated code back with a QR reader and it decodes to exactly the otpauth address, and nothing in PNLCS or its Docker image blocks a data: image. Thanks for the release-notes suggestion as well; it is noted for the next release.

Full suite green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants