Skip to content

FIDO2 Server Configuration#

This document contains a detailed reference of configuration properties. To learn how to safely update these dynamic configuration parameters, please follow the Janssen FIDO2 Configuration Guide first.

FIDO2 Server Configuration Parameters#

The following properties represent the dynamic configuration for the Janssen FIDO2 Server. They are stored in the configuration storage layer and can be updated at runtime.

Field Name Type / Example Description
issuer https://my-jans-server.jans.io URL using the HTTPS scheme with no query or fragment component. The OP asserts this as its Issuer Identifier.
baseEndpoint https://my-jans-server/jans-fido2/restv1 Base URL of the FIDO2 server endpoints.
webAuthnEndpoint https://my-jans-server/jans-fido2/restv1/webauthn/configuration Base URL of the FIDO2 WebAuthn Server Endpoint which returns Relying Party (RP) Origins.
cleanServiceInterval 60 Time interval for the Clean Service daemon in seconds.
cleanServiceBatchChunkSize 10000 Number of expired records fetched and deleted per iteration batch from the persistence store.
useLocalCache true Boolean value specifying whether to enable local in-memory caching for performance.
disableJdkLogger true Boolean value specifying whether to disable standard JDK loggers.
loggingLevel INFO, DEBUG, or TRACE The logging verbosity level for the FIDO2 server diagnostics.
loggingLayout text or json Format of output log lines (plain text layout or structured JSON).
externalLoggerConfiguration String Path to an external Log4j2 XML configuration file.
metricReporterInterval 300 The interval for the legacy jans-core metric reporter daemon in seconds.
metricReporterKeepDataDays 15 The retention period in days for legacy metric reporter records stored in persistence.
metricReporterEnabled true Boolean value specifying whether to enable the legacy jans-core metric reporter.
fido2MetricsEnabled true Master switch for passkey telemetry collection. If false, no metric entries are stored. See Passkey Telemetry & Metrics.
fido2MetricsAggregationEnabled true Enables the scheduled hourly/daily/weekly/monthly aggregation jobs for passkey telemetry. The times those jobs run are fixed by cron expressions packaged with the server, not by dynamic configuration — see Aggregation schedule.
fido2MetricsRetentionDays 90 Retention period in days for passkey metric entries and aggregations before automatic cleanup.
fido2DeviceInfoCollection true Whether device info (browser, OS, device type) is collected and stored with passkey metrics.
fido2ErrorCategorization true Whether passkey operation failures are categorized for the error-analysis endpoint.
fido2PerformanceMetrics true Whether passkey operation durations are tracked for performance analytics.
trustedProxyEnabled true, false, or unset Whether forwarded proxy headers may be trusted when recording the client IP on a metrics entry. Unset keeps the legacy behaviour of trusting them unconditionally; false never reads them; true trusts them only from the addresses in trustedProxyIpRanges. See Client IP in metrics.
trustedProxyIpRanges ["10.0.0.0/8", "192.168.1.0/24"] Reverse-proxy source addresses whose forwarded headers are trusted, in CIDR notation. Only consulted when trustedProxyEnabled is true; an empty list trusts nothing.
fido2Configuration Object Nested object containing FIDO2 protocol-specific details (see structure below).

Client IP in metrics#

The ipAddress recorded on a passkey metrics entry is taken from forwarded proxy headers (X-Forwarded-For and several older equivalents) before falling back to the address the request actually came from. Those headers are set by whoever sends the request, so trustedProxyEnabled controls whether they are believed:

Value Behaviour
unset (default) Headers are trusted unconditionally. Any caller able to reach a FIDO2 endpoint can choose the address recorded against its own ceremony.
false Headers are ignored; the connecting address is recorded.
true Headers are read only when the connecting address falls inside trustedProxyIpRanges. An empty list trusts nothing.

The default is unset so that upgrading changes nothing. Deployments that want the recorded address to be trustworthy must set this explicitly.

When trusted, X-Forwarded-For is read right to left: hops that are themselves listed as trusted proxies are skipped, and the first remaining address is recorded. Reading from the right matters because a client can prepend any value before the real proxy appends to the chain — so the leftmost entry is only used when no closer untrusted hop exists, such as a single-entry header from a trusted proxy.

X-Forwarded-For is the only header consulted in this mode. The older alternatives (Proxy-Client-IP, WL-Proxy-Client-IP and the HTTP_* variants) are ignored, because a reverse proxy overwrites X-Forwarded-For but passes other request headers through as the client sent them. Legacy mode still reads all of them. If nothing usable is found, the connecting address is recorded.

Ranges accept IPv4 and IPv6 CIDR notation; a bare address is treated as a full-length mask. An IPv4-mapped IPv6 address such as ::ffff:10.1.2.3 matches an IPv4 range, since a dual-stack JVM may report the connecting address in that form.

A range may also be written in that form. Its prefix is then read on whichever scale it can only mean, so an existing IPv4-scale range keeps working:

Prefix on a mapped range Read as
0–32 IPv4 scale, as written — ::ffff:10.0.0.0/8 selects 10.0.0.0/8
96–128 IPv6 scale, less the 96 bits of the mapping — ::ffff:10.0.0.0/104 also selects 10.0.0.0/8
33–95 Rejected and logged: the prefix covers part of the mapping itself, so it means nothing on either scale

Both sides of a comparison must be IP literals — a hostname is rejected and logged rather than resolved, because this runs on the request path.

Note on a common topology. Where a reverse proxy runs on the same host, requests reach the FIDO2 server from 127.0.0.1, and so do any sent directly to it. Trusting loopback therefore does not, on its own, distinguish the proxy from a direct caller.

The Authorization Server and Casa are callers too. Neither is a browser-facing reverse proxy, but both relay passkey ceremonies to FIDO2 and send the connecting address they observed on the browser's request (getRemoteAddr(), not a browser-supplied header) as X-Forwarded-For on that call — see Request context on raw entries. If trustedProxyEnabled is true, their addresses need to be in trustedProxyIpRanges as well, the same as any other trusted hop, or their forwarded value is ignored in favor of the connecting address.


FIDO2 Configuration Object (fido2Configuration)#

This nested block defines WebAuthn and FIDO2 attestation and assertion policy behavior.

Field Type Default / Example Description
authenticatorCertsFolder String "/etc/jans/conf/fido2/authenticator_cert" Folder where verified authenticator certificates (e.g., Apple roots) are stored.
mdsCertsFolder String "/etc/jans/conf/fido2/mds/cert" Folder where FIDO Metadata Service (MDS) TOC root certificates are stored.
mdsTocsFolder String "/etc/jans/conf/fido2/mds/toc" Folder where downloaded MDS TOC files are cached.
userAutoEnrollment Boolean false Specifies whether to automatically enroll unknown users during WebAuthn cycles (normally disabled).
unfinishedRequestExpiration Integer 120 Expiration time in seconds for incomplete registration/authentication requests.
metadataRefreshInterval Integer 1296000 Expiration time in seconds (e.g., 15 days) before checking and reloading the FIDO Alliance MDS TOC.
serverMetadataFolder String "/etc/jans/conf/fido2/server_metadata" Folder where local vendor metadata statement JSON files are placed manually.
enabledFidoAlgorithms Array of Strings ["RS256", "ES256"] Enabled cryptographic signing algorithms allowed for credentials. Accepted names: RS256, RS384, RS512, RS65535, PS256, PS384, PS512, ES256, ES384, ES512, ESP256, ESP384, EdDSA, Ed25519, Ed448, ML-DSA-44, ML-DSA-65, ML-DSA-87 — the algorithms the server can both advertise and complete a registration with. When unset, the server advertises RS256, ES256 and EdDSA. An unrecognised name is ignored. A recognised name the deployment cannot actually complete a registration with is logged at ERROR and left out of pubKeyCredParams — see Advertised algorithms.
rp Array of Objects [ { "id": "https://jans.io", "origins": ["jans.io"] } ] Relying Party (RP) configuration mapping expected IDs to valid origins. Each entry may also carry a policy object — see Per-relying-party policy. It may also list androidApps and iosApps — see Native applications of a relying party.
metadataServers Array of Objects [ { "url": "https://mds.fidoalliance.org/" } ] External FIDO Metadata Service endpoints to download statement catalogs.
disableMetadataService Boolean false If set to true, the FIDO2 server skips validating authenticators against the MDS3 service.
mdsDownloadStartupRetries Integer 3 Number of times the MDS TOC download is retried at server startup when the TOC blob is missing (a missing TOC prevents attestation validation). This is in addition to the initial attempt, so the default of 3 means up to 4 downloads. 0 disables retries. Retries stop early once the blob is present, and are skipped when the metadata server answers HTTP 429, since it has explicitly asked the server to back off.
mdsDownloadStartupRetryInterval Integer 30 Delay in seconds between MDS TOC download retries at server startup when the TOC blob is missing.
hints Array of Strings ["security-key", "client-device", "hybrid"] Preferred authenticator type hints presented to the Relying Party.
enterpriseAttestation Boolean false Enables support for enterprise-specific hardware attestation profiles.
attestationMode String "monitor" Options are: disabled (skip attestation checks), monitor (log/validate but allow credentials if attestation is absent/unknown), and enforced (fail credential creation if attestation check fails).
allowedTopOrigins Array of Strings [] Full origins permitted to frame a cross-origin ceremony, each written as scheme, host and optional port (for example https://portal.example.com). Empty — the default — denies every framed ceremony. See Cross-origin ceremonies.
lockAuditEnabled Boolean false Enables delivery of passkey registration/authentication events to the Lock Server as audit evidence. See Lock Server audit delivery.
lockAuditEndpoint String "https://lock.example.com/audit" Base URL of the Lock Server audit endpoint; /log and /log/bulk are derived from it. Required when lockAuditEnabled is true.
lockAuditClientId String — OAuth2 client ID used to obtain a token, via the client credentials grant, for the https://jans.io/oauth/lock/log.write scope.
lockAuditClientPassword String — OAuth2 client secret paired with lockAuditClientId. Encrypted at rest, same as other client secrets, and deliberately excluded from configuration logging.
lockAuditFlushInterval Integer 20 Interval in seconds between batched deliveries of buffered Lock Server audit events. Delivery is asynchronous and batched, never one HTTP call per ceremony. Read once at server startup — unlike lockAuditEnabled, lockAuditEndpoint and the credential properties, which are re-read on every flush, changing this value requires a server restart to take effect.

Lock Server audit delivery#

When lockAuditEnabled is true, the server buffers passkey registration/authentication events and delivers them to the configured Lock Server's /audit/log/bulk endpoint on the lockAuditFlushInterval cadence, rather than one call per ceremony. A Lock Server that is unreachable, slow, or returns an error never affects the registration/authentication request itself — delivery is fire-and-forget, and a failed batch is dropped rather than retried inline.

No signing, hashing, or chaining of the delivered events is performed; that is planned as later work, not part of this delivery path.

Passkey registration outcomes (both successful and failed) are recorded as fido2_registration events, with decisionResult of ALLOW or DENY, principalId set to the username where known, and contextInformation carrying the relying party ID, origin, credential ID and attestation type on success. A failed registration records only the exception's class name, never its message, since some registration failure messages embed the username or challenge.

Passkey authentication outcomes (both successful and failed) are recorded as fido2_authentication events, with decisionResult of ALLOW or DENY, principalId set to the username where known, and contextInformation carrying the relying party ID, credential ID, and the origin the ceremony actually happened at — which is not necessarily the origin the credential was originally registered at, since a credential registered at one permitted origin of an RP can be used from a different permitted origin later. A failed authentication records only the exception's class name, never its message, since some authentication failure messages embed the username or challenge.

Per-relying-party policy#

An entry under rp may carry a policy object overriding the corresponding global setting for that relying party alone:

{
  "id": "high-assurance.example.com",
  "origins": ["https://high-assurance.example.com"],
  "policy": { "attestationMode": "enforced" }
}
Field Falls back to
attestationMode the global attestationMode

An RP with no policy, or with the field unset, behaves exactly as it did before this existed — the global value applies. The same is true for a ceremony whose origin matches no configured RP. Nothing needs changing on upgrade.

Only attestationMode is settable per RP today. Further fields are added alongside the change that enforces them, so that anything listed here is a policy actually in effect rather than a value that is merely stored.

Native applications of a relying party#

An entry under rp may also list the Android and iOS apps that share its RP ID, next to the web origins:

{
  "id": "example.com",
  "origins": ["https://login.example.com"],
  "androidApps": [
    {
      "packageName": "com.example.app",
      "sha256CertFingerprints": ["AB:CD:EF:..."],
      "distribution": "play-store"
    }
  ],
  "iosApps": [
    { "teamId": "T9A667JL6T", "bundleId": "com.example.app" }
  ]
}
Field Meaning
androidApps[].packageName Android application package name
androidApps[].sha256CertFingerprints SHA-256 fingerprints of the app signing certificate, colon-separated hex
androidApps[].distribution play-store or self-signed: which signing certificate the fingerprints belong to
iosApps[].teamId Apple Developer Team ID
iosApps[].bundleId Application bundle identifier

This is the single place an RP's native apps are recorded. It is what the assetlinks.json and apple-app-site-association files are meant to be generated from, and what a deployed copy of them can be checked against.

These fields are recorded but not enforced. Whether an origin is accepted is decided only by origins (see above); a package name, fingerprint, team ID or bundle ID listed here neither allows nor blocks a ceremony. When an origin is rejected, the server log says which RPs were considered and that configured native apps are not matched against it, to make that visible. An RP with neither list behaves exactly as it did before these fields existed, and nothing needs changing on upgrade.

Advertised algorithms#

The algorithms offered to the authenticator in pubKeyCredParams are not taken from enabledFidoAlgorithms directly. An algorithm is advertised only when this server can complete a registration with it end-to-end: decode the credential public key and verify a signature made with it, using the crypto provider the deployment is actually running. Anything else is dropped: a configured name that does not survive the check is logged at ERROR, and a default that does not survive it is logged at WARN.

This matters most on the FIPS build, whose provider supports strictly fewer algorithms than the standard one. Deriving the advertised set from real capability means a FIPS deployment simply offers less, rather than offering an algorithm and then failing the ceremony once the authenticator picks it.

If no configured algorithm survives the check, the server logs an error and falls back to whichever of the defaults it does support. In a deployment that supports none of them that fallback is itself empty, and pubKeyCredParams is sent empty — a state worth alerting on, since the log will already carry the reason.

Fully-specified ECDSA algorithms#

ESP256 and ESP384 name their elliptic curve in the COSE code point itself rather than leaving it to the credential: ESP256 is P-256 only and ESP384 is P-384 only. A credential that pairs one of them with any other curve is rejected during registration. ES256, ES384 and ES512 are not fully specified and take whichever curve the key carries.

Fully-specified EdDSA algorithms#

EdDSA is the original COSE code point and takes whichever Edwards curve the credential carries — both Ed25519 and Ed448 keys are accepted under it. Ed25519 and Ed448 are separate, fully-specified code points that name their curve: a credential pairing Ed25519 with an Ed448 key, or the reverse, is rejected during registration.

Existing credentials are unaffected — they were registered under EdDSA, whose behaviour is unchanged.

Post-quantum algorithms (ML-DSA)#

ML-DSA-44, ML-DSA-65 and ML-DSA-87 are supported on the standard build. They are not available on the FIPS build, because no released bc-fips provider implements them yet.

This needs no special handling from an administrator. Because the advertised set is derived from real provider capability (see Advertised algorithms), a FIPS deployment that lists an ML-DSA name simply logs it at ERROR and leaves it out of pubKeyCredParams — it never offers an algorithm it would then fail to verify. The same configuration is therefore safe to share between the two build variants.

Both the IANA spelling (ML-DSA-44) and the underscore form (ML_DSA_44) are accepted.

Cross-origin ceremonies#

As WebAuthn Level 3 requires, the server reads the crossOrigin member of CollectedClientData. An absent member is treated as false; both a non-boolean value and an explicit null fail with invalid_request.

When crossOrigin is true, the ceremony is allowed only if its topOrigin — the origin of the page that framed it — appears in allowedTopOrigins. The request fails with cross_origin_not_allowed when:

  • allowedTopOrigins is empty, which is the default and denies every framed ceremony
  • topOrigin is absent, null, not a string, or blank
  • topOrigin is not listed

Entries are compared against the whole origin, ignoring case and surrounding whitespace. A different scheme or port is a different origin, so https://portal.example.com does not permit http://portal.example.com.

allowedTopOrigins is deliberately separate from the origins under rp. Those say which origin may serve a ceremony; this says which origin may frame one. Reusing the former would silently widen the framing policy of every existing deployment.