Skip to content

OFCS conformance status

go-oidc-provider is regressed against the OpenID Foundation Conformance Suite (OFCS). The harness lives in conformance/ in the source repo and scaffolds nine OFCS plans; the published baseline below is the v1.0.0 release snapshot run end-to-end against a cmd/op-demo instance.

Personal project, not certified

This is a personal project maintained by an individual developer. No OpenID Foundation membership fee is paid and no formal OIDC certification is held. The numbers on this page are reproducible snapshots from the plan set shown below. They are not a substitute for a paid OpenID Foundation certification and should not be cited as one.

How to read the OFCS snapshot: nine plans in four spec families run against op-demo, and every module lands in exactly one of five outcomes — PASSED, REVIEW, SKIPPED, no verdict, or FAILED.TEST PLANS (9)OUTCOME PER MODULEOIDC Core 1.06 plansFAPI 2.0 Baseline1 planFAPI 2.0 Message Signing1 planFAPI-CIBA1 plancmd/op-demothe OP under testPASSEDREVIEWSKIPPEDNo verdictFAILED

What this snapshot exercises

PlanWhat it coversProfile
oidcc-basic-certification-test-planAuthorization Code + PKCE, ID Token, UserInfo, refresh, discoveryOIDC Core 1.0
oidcc-config-certification-test-planThe discovery document and the shape of the published JWKSOIDC Core 1.0 — Discovery
oidcc-dynamic-certification-test-planDynamic client registration, then the code flow against the client it registeredOIDC Core 1.0 — Dynamic Registration
oidcc-formpost-basic-certification-test-planThe basic plan again with response_mode=form_postOIDC Core 1.0 — form_post
oidcc-rp-initiated-logout-certification-test-plan/end_session, id_token_hint, post_logout_redirect_uriRP-Initiated Logout 1.0
oidcc-backchannel-rp-initiated-logout-certification-test-planRP-initiated logout plus logout_token delivery to the registered back-channel URIBack-Channel Logout 1.0
fapi2-security-profile-id2-test-plan+ PAR, sender-constrained access tokens (DPoP), strict alg list, redirect_uri exact matchFAPI 2.0 Baseline
fapi2-message-signing-id1-test-plan+ JAR (signed authorization request), JARM (signed authorization response)FAPI 2.0 Message Signing
fapi-ciba-id1-test-planClient-Initiated Backchannel Authentication (poll mode), mTLS-bound tokensFAPI-CIBA

Latest baseline

209 of 271modules PASSED across nine plans — 77.1% of the run
  • PASSED20977.1%
  • REVIEW3914.4%
  • SKIPPED155.5%
  • No verdict20.7%
  • FAILED62.2%
  • WARNING00.0%

Every FAILED and no-verdict module is a reviewed release exclusion — three causes, none of them an unresolved defect.

Captured2026-07-26T23:22:11ZRepository SHA3ccc6bcOFCS imagerelease-v5.2.1

Snapshot status

The bars are the raw outcome of all nine plans. The matching timestamped JSON is under conformance/baselines/. make conformance-release-verify accepted this release snapshot with zero blockers: every raw failure and no-verdict module has a reviewed, expiry-bound entry in conformance/release-exclusions.json. That gate result is not an OIDC certification.

Why six modules FAILED

Every failing and no-verdict module traces back to one of three causes. Two are standing design decisions that will not change in the 1.x line; the third is a limit of driving the suite headlessly. None of them is an unresolved defect, and each one is listed with an owner and an expiry date in conformance/release-exclusions.json.

ES256-only signing — permanent, not a staged rollout

Discovery advertises ES256 and nothing else, so the modules that assert the OIDC Core 1.0 §15.1 RS256 requirement fail, and a dynamic registration asking for an RS256-signed ID Token or UserInfo response is refused before it reaches its assertions. Supporting one algorithm removes algorithm negotiation — and the downgrade guard negotiation would need — and FAPI 2.0 forbids RS256 outright. Why the alg list is closed

4 FAILED1 no verdict
Back-channel logout is subject-scoped, not session-scoped

The OP advertises backchannel_logout_session_supported: false and logs out by subject rather than by sid. That metadata is optional in the spec, but the certification module requires true unconditionally, and the rest of the plan aborts on the same assertion. Session-scoped sid logout is an opt-in profile planned for a later minor; subject-only logout is the fail-secure default. Back-channel logout

2 FAILED
A key rotation the harness cannot perform

oidcc-server-rotate-keys asks a human to rotate the signing keys mid-module and press Start. The library takes its keyset at op.New, so a deployment rotates by constructing a new provider — which would drop the in-memory store the plan's registered client lives in. What the OP serves is unaffected: JWKS publishes every configured key. Key rotation

1 no verdict

Per-plan breakdown

Bar length is the plan's module count on one shared scale, so a one-module plan cannot read as heavily as a seventy-one-module one. Outcomes with a count of zero are left out of the row.

oidcc-basic-certification-test-plan · 35 modules29 PASSED4 REVIEW2 SKIPPED
oidcc-config-certification-test-plan · 1 module1 FAILED
oidcc-dynamic-certification-test-plan · 23 modules7 PASSED6 REVIEW5 SKIPPED2 no verdict3 FAILED
oidcc-formpost-basic-certification-test-plan · 35 modules30 PASSED3 REVIEW2 SKIPPED
oidcc-rp-initiated-logout-certification-test-plan · 11 modules3 PASSED8 REVIEW
oidcc-backchannel-rp-initiated-logout-certification-test-plan · 2 modules2 FAILED
fapi2-security-profile-id2-test-plan · 58 modules48 PASSED9 REVIEW1 SKIPPED
fapi2-message-signing-id1-test-plan · 71 modules60 PASSED9 REVIEW2 SKIPPED
fapi-ciba-id1-test-plan · 35 modules32 PASSED3 SKIPPED
The same snapshot as a table
PlanPASSEDREVIEWSKIPPEDWARNINGFAILEDNo verdictTotal
oidcc-basic-certification-test-plan294200035
oidcc-config-certification-test-plan0000101
oidcc-dynamic-certification-test-plan76503223
oidcc-formpost-basic-certification-test-plan303200035
oidcc-rp-initiated-logout-certification-test-plan38000011
oidcc-backchannel-rp-initiated-logout-certification-test-plan0000202
fapi2-security-profile-id2-test-plan489100058
fapi2-message-signing-id1-test-plan609200071
fapi-ciba-id1-test-plan320300035
Total2093915062271

What each test plan covers

Each OFCS test plan exercises a specific spec profile. The tables below map every plan to the library options that turn on the relevant code paths and to the doc pages where that surface is documented, so embedders can verify their own deployments expose the same configuration the suite asserts against.

oidcc-basic-certification-test-plan — OIDC Core 1.0

What it testsLibrary option to enableDoc page
Authorization Code flow + PKCEenabled by default/concepts/authorization-code-pkce
ID Token issuance + claimsenabled by default/concepts/tokens
UserInfo endpointenabled by default/concepts/tokens
Discovery (/.well-known/openid-configuration)enabled by default/concepts/discovery
JWKS publicationenabled by default/operations/jwks
Refresh tokens + rotationenabled by default; long-lived refresh requires the offline_access scope/concepts/refresh-tokens
Standard scopes (profile, email, address, phone)op.WithScope(...) once per scope/concepts/scopes-and-claims
Public / pairwise subjectsop.WithPairwiseSubject(salt) for pairwise; per-client SubjectType selects which one applies/use-cases/pairwise-subject

fapi2-security-profile-id2-test-plan — FAPI 2.0 Baseline

What it testsLibrary option to enableDoc page
PAR (RFC 9126)op.WithProfile(profile.FAPI2Baseline) implies feature.PAR/concepts/fapi, /use-cases/fapi2-baseline
JAR (RFC 9101)profile implies feature.JAR/concepts/fapi
S256 PKCE enforcementprofile-enforced/concepts/authorization-code-pkce
iss in authorization response (RFC 9207)profile-enforced/concepts/issuer
ES256 for ID Token signingprofile-enforced; OP-issued ID Tokens are never signed with PS256 or RS256/concepts/jose-basics
Refusal of RS256 (FAPI), HS*, noneclosed alg type at internal/jose/alg.go/security/design-judgments
private_key_jwtprofile-enforced/concepts/client-types
DPoP or mTLS sender constraintop.WithFeature(feature.DPoP) or op.WithFeature(feature.MTLS) (at least one is mandatory under FAPI 2.0)/concepts/sender-constraint, /concepts/dpop, /concepts/mtls
redirect_uri exact matchprofile-enforced/concepts/redirect-uri
Refresh token rotation + reuse detectionenabled by default/concepts/refresh-tokens

fapi2-message-signing-id1-test-plan — FAPI 2.0 Message Signing

Message Signing layers signed authorization responses on top of Baseline. Everything the Baseline plan asserts also runs here — switch the profile constant and JARM activates automatically.

What it testsLibrary option to enableDoc page
Everything from FAPI 2.0 Baseline (above)op.WithProfile(profile.FAPI2MessageSigning)(as above)
Signed authorization response (JARM)profile implies feature.JARM/concepts/fapi (JARM section)
Signed ID Token in token responseprofile-enforced/concepts/tokens
Request object signing (PS256 / ES256)profile-enforced/concepts/fapi

fapi-ciba-id1-test-plan — FAPI-CIBA (Client-Initiated Backchannel Authentication)

The CIBA plan exercises the OpenID Connect Client-Initiated Backchannel Authentication grant: an authentication request initiated by the client, completed asynchronously on the user's authentication device (push notification, IVR, etc.), and consumed back via a polling token request. The OP runs in poll mode, FAPI-CIBA inherits FAPI 1.0's hardcoded tls_client_certificate_bound_access_tokens requirement so mTLS sender constraint is mandatory.

What it testsLibrary option to enableDoc page
/bc-authorize endpoint + auth_req_idop.WithCIBA(op.WithCIBAHintResolver(...))/use-cases/ciba
Hint resolution (login_hint / id_token_hint / login_hint_token)embedder-supplied HintResolver/use-cases/ciba
Polling discipline (authorization_pending / slow_down)enabled by default; op.WithCIBAPollInterval(...) overrides advertised interval/use-cases/ciba
Poll-abuse lockout capdefault 5 strikes; op.WithCIBAMaxPollViolations(n) raises or lowers the cap/use-cases/ciba
tls_client_certificate_bound_access_tokens (FAPI-CIBA mandate)op.WithProfile(profile.FAPICIBA) implies feature.MTLS/concepts/mtls
Signed request object on /bc-authorizeop.WithFeature(feature.JAR) (auto under FAPI-CIBA)/concepts/fapi
Bound request_object iat and exp claims (FAPI-CIBA §5.2.2)profile-enforced/concepts/fapi

How REVIEW, SKIPPED, WARNING, and FAILED categorize

  • REVIEW — the test ran, but a human reviewer must verify visual or out-of-band behaviour the harness cannot capture honestly (consent UI strings, error page screenshots, certificate chain confirmation). Not a failure.
  • SKIPPED — the test depends on a feature this OP does not advertise in discovery or per-client metadata. For example, the RS256 negative tests skip because the FAPI client metadata declares PS256 as its signing alg, putting RS256 out of scope for that probe. Not a failure.
  • WARNING — OFCS records this as a non-failed result value: the test reached a terminal PASS on its main assertions but logged an advisory the operator may want to address. The current snapshot has 0 warnings.
  • FAILED — a module did not reach the suite's expected result. In the v1.0.0 snapshot, six failures are reviewed release exclusions: four arise from the permanent ES256-only signing policy and two from the deliberately subject-scoped back-channel logout metadata. The release verifier fails unless each exception is explicitly listed and unexpired.
  • No verdict — the harness could not produce a terminal result. The two current modules require an in-process signing-key rotation or wait for an RS256 request object that the ES256-only OP correctly refuses. Both are reviewed release exclusions.

How to reproduce the conformance run yourself

  1. Stand up an OP with the relevant profile wired in — op.WithProfile(profile.FAPI2Baseline) for the security profile, op.WithProfile(profile.FAPI2MessageSigning) for message signing, op.WithProfile(profile.FAPICIBA) plus the CIBA options for FAPI-CIBA, or no WithProfile for the OIDC Core plan.
  2. Register the plan against an OFCS deployment. The conformance suite is operated by the OpenID Foundation; the source repo's conformance/ directory contains plan templates and a pinned Docker image that brings up a local copy.
  3. Drive the plan. The harness pokes /authorize, /par, /token, /userinfo, /jwks, and the rest of the discovered endpoints through every required code path, then writes a JSON snapshot you can diff against the recorded baseline.

The detailed runbook (make targets, the JSON snapshot layout, the diff gate) is in Reproducing the baseline yourself below.

REVIEW vs FAILED — the distinction

OFCS mainly reports PASSED, FAILED, REVIEW, and SKIPPED; the harness also preserves WARNING when OFCS emits an advisory result. REVIEW does not mean a test failed. It means the test wants a human operator to confirm something the automation cannot — for example, "did the OP show a login screen here?" The test runs, takes screenshots, then sits in a WAITING state until someone in the OFCS UI clicks "reviewed". Our headless runner records REVIEW when the test reached that state without erroring.

Why we don't auto-pass REVIEW modules

The conformance suite gates these modules on human judgment by design. A cmd/op-demo running headless can't honestly upload a screenshot of "this is what my user saw"; turning the gate off would lie about what was actually checked. The harness records REVIEW as-is, on the understanding that paid certification would require sitting in front of the UI to clear them.

Modules currently FAILED — and why

Six modules across three plans. Each row is a reviewed, expiry-bound entry in conformance/release-exclusions.json; make conformance-release-verify fails the release if any of them is missing, unowned, or past its expiry.

ModulePlanWhy it fails
oidcc-discovery-endpoint-verificationoidcc-configid_token_signing_alg_values_supported lists ES256 only; the module asserts the OIDC Core 1.0 §15.1 RS256 requirement.
oidcc-discovery-endpoint-verificationoidcc-dynamicSame assertion, same cause, on the dynamic plan.
oidcc-idtoken-rs256oidcc-dynamicRegistration requests id_token_signed_response_alg=RS256; dynamic client registration answers invalid_client_metadata, so the module never reaches its assertions.
oidcc-userinfo-rs256oidcc-dynamicSame, for userinfo_signed_response_alg=RS256.
oidcc-backchannel-logout-discovery-endpoint-verificationoidcc-backchannel-rp-initiated-logoutThe OP advertises backchannel_logout_session_supported: false, which the spec permits and this module refuses.
oidcc-backchannel-rp-initiated-logoutoidcc-backchannel-rp-initiated-logoutCascades from the module above — the plan aborts on that assertion before this one can run.

Modules with no verdict — and why

Two modules the harness could not drive to a terminal result. Both are recorded with the evidence from a standalone run against a live suite.

ModulePlanWhy there is no verdict
oidcc-server-rotate-keysoidcc-dynamicThe module waits for an operator to rotate the signing keys and press Start. The keyset is fixed at op.New, and rebuilding the provider mid-plan would drop the in-memory store holding the plan's registered client. The module stays CONFIGURED until the runner's idle bound.
oidcc-request-uri-signed-rs256oidcc-dynamicThe module pushes an RS256-signed request object and waits for a successful authorization response. The ES256-only OP refuses before redirecting — the correct behaviour — so no callback ever arrives and the suite records no result either way.

Modules currently in REVIEW

oidcc-basic plan (4)

ModuleWhat it gates
oidcc-ensure-registered-redirect-uriManual confirmation that the OP refused an unregistered redirect_uri
oidcc-max-age-1Manual confirmation that max_age=1 re-prompted the user
oidcc-prompt-loginManual confirmation that prompt=login re-prompted
oidcc-response-type-missingManual confirmation of the first-party error page for a request without response_type

FAPI 2.0 plans (9 each, same set)

These all gate on a screenshot upload of the OP's error page or a manual "is the user actually re-prompted" judgment. They run cleanly headless but stay REVIEW until human sign-off (the same nine names appear on both fapi2-security-profile-id2 and fapi2-message-signing-id1, totalling 18 across the two plans):

  • fapi2-…-ensure-different-nonce-inside-and-outside-request-object
  • fapi2-…-ensure-different-state-inside-and-outside-request-object
  • fapi2-…-ensure-request-object-with-long-nonce
  • fapi2-…-ensure-request-object-with-long-state
  • fapi2-…-ensure-unsigned-authorization-request-without-using-par-fails
  • fapi2-…-par-attempt-reuse-request_uri
  • fapi2-…-par-attempt-to-use-expired-request_uri
  • fapi2-…-par-attempt-to-use-request_uri-for-different-client
  • fapi2-…-state-only-outside-request-object-not-used

The OP returns the right HTTP error in every case (the negative tests pass their internal assertions); OFCS just wants a human to inspect the rendered error UI.

Modules currently in WARNING

None in the current snapshot. The previous fapi-ciba-id1-refresh-token advisory is now a plain PASSED module.

Modules currently SKIPPED — and why

ModuleReason
fapi2-…-ensure-signed-client-assertion-with-RS256-fails (×2)The FAPI client used in the plan registers token_endpoint_auth_signing_alg=PS256, so OFCS skips the per-client RS256 negative test on both fapi2 plans.
fapi2-message-signing-…-ensure-signed-request-object-with-RS256-failsSame — the FAPI client's request_object_signing_alg=PS256 makes the RS256 negative test inapplicable.
fapi-ciba-id1-ensure-request-object-signature-algorithm-is-RS256-failsThe FAPI-CIBA client registers request_object_signing_alg=PS256.
fapi-ciba-id1-ensure-client-assertion-signature-algorithm-in-backchannel-authorization-request-is-RS256-failsSame — token_endpoint_auth_signing_alg=PS256 on the CIBA client.
fapi-ciba-id1-ensure-client-assertion-signature-algorithm-in-token-endpoint-request-is-RS256-failsSame.
oidcc-ensure-request-object-with-redirect-uriThe oidcc-basic plan does not enable JAR; the OP omits request_object_signing_alg_values_supported from discovery and OFCS skips.
oidcc-unsigned-request-object-supported-correctly-or-rejected-as-unsupportedSame — JAR off, no request parameter, OFCS skips.

"SKIPPED" is intentional, not "didn't run"

OFCS's skip decision is a function of what discovery and per-client metadata advertise. The FAPI clients in the plan declare PS256 as their token-endpoint-auth and request-object signing alg, so OFCS's "RS256 should fail" probes are not applicable and the suite marks them skipped rather than running them and recording a pass.

Reproducing the baseline yourself

sh
git clone https://github.com/libraz/go-oidc-provider.git
cd go-oidc-provider
make conformance-up
make conformance-baseline LABEL=local-check
ls conformance/baselines/   # JSON snapshot lands here

The harness:

  1. Generates self-signed RSA-2048 certs (scripts/conformance.sh certs).
  2. Brings up the OFCS Docker stack at https://localhost:8443.
  3. Builds and runs cmd/op-demo at https://127.0.0.1:9443.
  4. Seeds the OFCS plans via the REST API. The harness scaffolds nine plans; the latest status table above records all nine.
  5. Records pass/fail per module to a deterministic JSON file.

make conformance-baseline-diff exits non-zero on any module that lost PASSED between two snapshots — that is the regression gate the project uses pre-merge for security-relevant changes.

What FAPI 2.0 means in this codebase

op.WithProfile(profile.FAPI2Baseline) activates the configuration the two fapi2-* plans are built around:

  • feature.PAR (auto-enabled by FAPI2Baseline) — /par becomes routable; request_uri accepted at /authorize
  • feature.JAR (auto-enabled by FAPI2Baseline) — request / request_uri validated as signed JWTs
  • feature.JARM (additionally auto-enabled by FAPI2MessageSigning) — authorization responses signed as JWTs
  • Sender-constrained access tokens — the profile imposes a DPoP-or-mTLS requirement. If the embedder explicitly enables feature.MTLS (cnf.x5t#S256), that satisfies the requirement and suppresses the DPoP default. Otherwise op.New selects feature.DPoP (cnf.jkt) as the canonical default, so a plain op.WithProfile(profile.FAPI2Baseline) still boots with sender-constrained access tokens. Discovery advertises dpop_signing_alg_values_supported: ES256, EdDSA, PS256 when DPoP is active.
  • JOSE alg allow-list locked to RS256 / PS256 / ES256 / EdDSA codebase-wide; HS* and none are structurally unreachable (see internal/jose/alg.go)
  • token_endpoint_auth_methods_supported intersected with the FAPI production path (private_key_jwt)
  • redirect_uri exact-string match enforced
  • per-client RequestObjectSigningAlg / TokenEndpointAuthSigningAlg narrowing pins each FAPI client to PS256 (or ES256 / EdDSA); the discovery doc still advertises the codebase-wide list

If you set conflicting options after WithProfile, op.New(...) returns a build-time error rather than letting a partial-FAPI configuration escape into production.

Where the harness lives

PathWhat it is
conformance/README.mdOperator runbook
conformance/plans/*.jsonPlan templates (server / client / resource blocks)
conformance/docker-compose.ymlOFCS image pin (release-v5.2.1) + JKS truststore wiring
scripts/conformance.shcerts / ofcs-up / op-up / seed-plans / drive / batch
tools/conformance/ofcs.pyREST client + headless drive script
conformance/baselines/*.jsonCaptured snapshots (gitignored — environment-specific)

Caveats worth naming

  • Plan suite version. OFCS is pinned to release-v5.2.1. Tests added or renamed in newer OFCS releases are not covered until the pin is bumped.
  • Headless drive. The drive script reverse-engineers the OFCS REST surface; OFCS does not document it. Behaviour is confirmed against v5.2.1 only.
  • No real RP cert. The mTLS plan slots use generated self-signed certs at conformance/certs/ so the plan can be instantiated. No real CA chain is exercised.
  • Single OP instance. Cross-instance behaviour (e.g. token introspection across two OPs sharing a store) is exercised by test/scenarios, not OFCS.

The conformance harness sits next to an in-process Spec Scenario Suite under test/scenarios/. The two suites cover different layers — OFCS runs end-to-end against a live OP via HTTP, the scenario suite drives the same protocol invariants in-process — and both must be passing before security-relevant changes merge.