Skip to main content
Running the integration on a laptop before rollout is sensible. But nearly every difficulty of a local setup is created by the setup itself, not by Veriqa.
Everything on this page is about local testing. In production, where Veriqa has a real domain and a valid certificate, none of it applies: the production setup is simpler than the local one.

The certificate

A self-signed certificate has to be mounted inside every container, followed by update-ca-certificates — otherwise PHP’s cURL breaks the code-for-tokens exchange. In production this step does not exist at all.
The system trust store is not enough for everyone. update-ca-certificates puts the CA where PHP and Ruby look, but not every runtime reads that store. Python clients built on requests — Odoo’s OIDC module, for one — use their own certifi bundle and keep breaking the code exchange even with the CA installed; they need REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt. The rule is general: the trust store belongs to the runtime, so check the one the client is written in, not the one the site runs on.
A separate trap: antivirus software that intercepts TLS replaces your self-signed certificate with its own and shows an “untrusted certificate” interstitial. The “continue” link on such a page drops the query string — that is, the whole OIDC request — so you cannot get past it, either by hand or programmatically. Server-side calls are unaffected: PHP inside the container uses the mounted CA and never sees the problem. A workaround that does not touch the machine’s security settings: expose two HTTPS endpoints — one with your own certificate for the containers, and one with a dev certificate already trusted by the system for your own browser.

The issuer address

Three rules:
  • the issuer must be https. On http://localhost:5200 the core refuses even in Development. Note that a redirect_uri over http is still accepted — the restriction is about the issuer address specifically;
  • the address must resolve identically from inside the container and from the host, or the iss in discovery drifts away from what the browser sees;
  • a private address is blocked by clients. host.docker.internal is a private network, and almost every system guards against calls there with a flag of its own: WordPress cuts such requests off via wp_safe_remote_post() (fixed by the allow_internal_idp setting), Drupal via allow_private_issuer, Nextcloud via allow_local_remote_servers, Discourse via allowed_internal_hosts, Moodle via curlsecurityblockedhosts. In production the address is public and none of these flags are needed.
One exception that does not go away in production. Moodle restricts outbound requests not only by address but also by port: curlsecurityallowedport allows just 443 and 80 by default. If your issuer listens on a non-standard port, that port has to be allowed explicitly on the production address too.

Hop mode on a local setup

Hop mode is worth trying locally — it is the setting we recommend for production. It adds one condition to the issuer rules above: the QR code carries a link to the issuer address, and the phone opens it. A phone cannot reach localhost on your machine, and it does not trust your self-signed certificate. Put Veriqa behind an HTTPS tunnel — any service that gives a local port a public https address with a valid certificate — and set that address as the issuer (Veriqa:OpenIddict:Server:Issuer). The rule about one address everywhere applies here too: the browser, the containers and the phone must all see the tunnel address, not localhost. Then switch hop mode on with Veriqa:HopMode:Enabled = true. In production none of this is needed: the issuer is already a public https address.

Environment odds and ends

Mail

For the Email channel it is convenient to run a local mail catcher instead of real SMTP: nothing leaves the machine, and the link from the message is visible in its web UI.
On SMTP without authentication mail delivery works every other time: the connection is reused incorrectly and every second attempt returns an error. A repeat request goes through. This is a known limitation, not a consequence of your configuration — see Troubleshooting.
One more: the Email channel has a rate limit, so debugging with several requests in a row will produce a 429. Leave a pause between attempts.

What a local setup does not verify

  • mail deliverability — SPF, DKIM, DMARC and domain reputation mean nothing to a local catcher;
  • vendor platform behavior — Shopify and Wix reach Veriqa from the outside and need its public HTTPS address, see SaaS builders;
  • performance under load — local response numbers say nothing about production.