OpenID conformance tests
pforte is tested against the official OpenID conformance suite (Basic OP profile). The suite acts as an application and calls pforte, so pforte has to be reachable from the internet while a test runs. This is done with a Cloudflare tunnel to a separate pforte instance with its own database and keys, never your development data.
The suite cannot run in CI. A run is manual, takes about an hour, and is repeated before a release.
One-time setup
-
A Cloudflare tunnel for
conformance.pforte.devpointing athttp://localhost:3301. The domain must use Cloudflare DNS.brew install cloudflared cloudflared tunnel login cloudflared tunnel create pforte-conformance cloudflared tunnel route dns pforte-conformance conformance.pforte.devThen create
~/.cloudflared/config.yml(replace<UUID>with the tunnel's id):tunnel: <UUID> credentials-file: /Users/<you>/.cloudflared/<UUID>.json ingress: - hostname: conformance.pforte.dev service: http://localhost:3301 - service: http_status:404Never commit anything from
~/.cloudflared; the credentials file is a login for the tunnel. -
An account on the suite: sign in at https://www.certification.openid.net with a Google or GitLab account.
Each run
-
Start the local Postgres and prepare the conformance instance. This creates the separate database
pforte_conformance,.env.conformancewith fresh keys, and the clients and test user the suite needs. It prints their values, new on every run.pnpm dev:up pnpm conformance:setup -
Start pforte for the suite and the tunnel, each in its own terminal:
pnpm conformance:start cloudflared tunnel run pforte-conformanceCheck https://conformance.pforte.dev/.well-known/openid-configuration in a browser.
-
In the suite choose Create a new test plan and the plan OpenID Connect Core: Basic Certification Profile Authorization server test. Variants:
client_secret_basic, response typecode, server metadatadiscovery, static client registration. Enter the values printed bypnpm conformance:setup: the alias, the discovery URL, client 1 and client 2 with their secrets, and for theclient_secret_postclient use client 1 again (pforte accepts the secret in either place). -
Run the tests one by one. Read the blue box at the top of each test; most only need you to sign in on pforte's login page with the printed test user. Some tests need a screenshot.
Signing in has a second step: pforte asks for a 6-digit code. The conformance instance has no mail or SMS, so it writes the code to the terminal where
pnpm conformance:startruns, in a line with[development notifier] Sign-in code for …. Copy the code from there. To find it among the other log lines, start pforte through a filter instead:pnpm conformance:start 2>&1 | grep --line-buffered 'development notifier'Tick Remember this device when you enter the first code (
conformance:startticks it by default). The suite runs all tests in the same browser, so later sign-ins ask for the password only. If you delete the cookies ofconformance.pforte.dev, the next sign-in asks for a code again. -
Stop the tunnel and pforte when you are done. The conformance instance has no rate limiting yet, so do not leave it running.
Tips for a run
-
The second factor is part of every sign-in, including the tests that need a new sign-in (
oidcc-prompt-login,oidcc-max-age-1): with a remembered device they ask for the password only, the others show the code step. The ID token then carriesacrurn:pforte:acr:pwdandamr["pwd"]for a remembered device, andurn:pforte:acr:mfaand["pwd","otp"]after a code. -
The suite runs all tests in the same browser, so pforte's session cookie stays between tests. This is intended for most tests, but
oidcc-prompt-none-not-logged-inasks you to remove the provider's cookies first, and it fails with "expected an error but got a code" otherwise. Run that test in a private window, or delete the cookies ofconformance.pforte.devbefore it. -
Restart
pnpm conformance:startafter every code change; the instance does not reload itself. -
Do not run other commands that stop or start pforte on port 3301 while a plan is running.
-
Test
oidcc-max-age-1needs the previous sign-in to be more than a couple of seconds old (the library compares whole seconds).
Accepted warnings
Warnings are accepted for certification. These are known and intended:
oidcc-scope-profile,oidcc-scope-email: not all standard claims of the scope are returned. pforte only returns claims it holds (for example nopictureorbirthdate); inventing values would be wrong. (email_verifiedis returned since #6.)oidcc-scope-email: the e-mail is also in the ID token. Claims for the granted scopes are deliberately placed in the ID token so applications can read them without a second request.oidcc-claims-essential:nameis not in the userinfo, because pforte does not support theclaimsrequest parameter (claims_parameter_supportedis false) and ignores it, which is allowed. Applications get claims through scopes.oidcc-alternate-happy-flow: the same two warnings asoidcc-scope-email(the e-mail is also in the ID token; not all standard claims of the scope are returned).oidcc-scope-address,oidcc-scope-all: skipped, because pforte does not support theaddressscope (scope-allincludes it).oidcc-unsigned-request-object-supported-correctly-or-rejected-as-unsupported: skipped, because pforte does not support request objects (request_parameter_supportedis false).
PKCE and the suite's clients
pforte requires PKCE (S256) for every client by default. The suite's Basic profile does not send
PKCE, which OpenID Connect Core makes optional for confidential clients. The conformance seed
therefore registers its two clients with pkce_required = false. This is a per-client setting
that an administrator can also use for older applications that cannot send PKCE. Public clients
always require PKCE; the database enforces it. A client that is not required to send a challenge
but does is still verified.
The https requirement
OpenID Connect requires the authorization endpoint to accept POST as well as GET. A POST comes
from the application's site, so pforte's session cookie must be sent cross-site: SameSite=None,
which browsers only accept together with Secure. pforte therefore enables POST and sets that
cookie attribute when the issuer is an https URL, which every real deployment uses. For the http
issuer of local development, POST stays off.
Reading results
PASSED, REVIEW, WARNING and SKIPPED are acceptable; FAILED and INTERRUPTED are not.
Keep the link to each test's log. Record the outcome of each run below.
Results
Run of 2026-10-20: no failures
The run for the first release: a complete run on pforte commit 16970cc, the head of main when it started. The release
adds only the changelog entry to it, so the image is built from this code.
Plan oidcc-basic-certification-test-plan (id CbyR5vcTxLfZR), suite version 5.3.3 (revision 7d420f7), variant
server_metadata=discovery, client_registration=static_client, alias progwise-pforte, issuer https://conformance.pforte.dev.
The plan was started on 2026-10-20 at 18:46 (Berlin time); some tests were run on the following day. The table shows the latest
result of each test.
35 tests: 24 passed, 4 warnings, 4 to review, 3 skipped, 0 failed, 0 interrupted.
| Result | Tests |
|---|---|
| Passed | server, response-type-missing, userinfo-get, userinfo-post-header, userinfo-post-body, ensure-request-without-nonce-succeeds-for-code-flow, scope-phone, display-page, display-popup, prompt-none-not-logged-in, prompt-none-logged-in, max-age-10000, ensure-request-with-unknown-parameter-succeeds, id-token-hint, login-hint, ui-locales, claims-locales, ensure-request-with-acr-values-succeeds, codereuse, codereuse-30seconds, ensure-post-request-succeeds, server-client-secret-post, refresh-token, ensure-request-with-valid-pkce-succeeds |
| Warning (accepted, see above) | scope-profile, scope-email, alternate-happy-flow, claims-essential |
| Review | prompt-login, max-age-1, ensure-registered-redirect-uri, ensure-request-object-with-redirect-uri |
| Skipped | scope-address, scope-all, unsigned-request-object-supported-correctly-or-rejected-as-unsupported |
Compared with the run of 2026-10-16, this one has the second factor (#6) in every sign-in, and the ID token carries acr, amr
and email_verified. ensure-request-with-acr-values-succeeds therefore moved from warning to passed. The four tests marked
review only need a human to look at evidence (screenshots of the login page or of an error page); they are required for a formal
certification submission, which has not been made. The suite's "private link" for the plan is not published here, because the logs
contain the generated test credentials.
Earlier run of 2026-10-16: no failures
This run predates the second factor (#6) and is superseded by the run above.
Plan oidcc-basic-certification-test-plan, suite version 5.3.3, variant
server_metadata=discovery, client_registration=static_client, alias progwise-pforte,
issuer https://conformance.pforte.dev. The table shows the latest result of each test; tests
were repeated while the gaps listed below were fixed, so this was not one uninterrupted pass. The
final code is pforte commit 1f7811a. A clean full run is repeated before each release.
35 tests: 23 passed, 5 warnings, 4 to review, 3 skipped, 0 failed, 0 interrupted.
| Result | Tests |
|---|---|
| Passed | server, response-type-missing, userinfo-get, userinfo-post-header, userinfo-post-body, ensure-request-without-nonce-succeeds-for-code-flow, scope-phone, display-page, display-popup, prompt-none-not-logged-in, prompt-none-logged-in, max-age-10000, ensure-request-with-unknown-parameter-succeeds, id-token-hint, login-hint, ui-locales, claims-locales, codereuse, codereuse-30seconds, ensure-post-request-succeeds, server-client-secret-post, refresh-token, ensure-request-with-valid-pkce-succeeds |
| Warning (accepted, see above) | scope-profile, scope-email, alternate-happy-flow, ensure-request-with-acr-values-succeeds, claims-essential |
| Review | prompt-login, max-age-1, ensure-registered-redirect-uri, ensure-request-object-with-redirect-uri |
| Skipped | scope-address, scope-all, unsigned-request-object-supported-correctly-or-rejected-as-unsupported |
The four tests marked review only need a human to look at evidence (screenshots of the login page or of an error page). They are required for a formal certification submission, which has not been made. The suite's "private link" for the plan is not published here, because the logs contain the generated test credentials.
What the run found and fixed
The first runs exposed gaps that unit tests had missed, all fixed with tests:
- PKCE was required for every client, but the suite's confidential clients do not send it. It is
now a per-client setting (
pkce_required, default on; always on for public clients). - A browser that was already signed in and asked for more scopes was sent to a consent page that
does not exist. The grant is now extended, and
prompt=consentis accepted automatically. - The authorization endpoint did not accept POST, which OpenID Connect requires. It does now, for
https issuers (it needs a
SameSite=None; Securesession cookie). login_hintis used to prefill the e-mail field.