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 Requests relayed by the Authorization Server and Casa. 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.
nativeReadinessEnabled Boolean false Makes the native app readiness endpoint available. When false the endpoint does no work.
nativeReadinessAllowExternalFetch Boolean false Allows the readiness checks to make outbound requests to a relying party's /.well-known association files. Outbound requests need both this and nativeReadinessEnabled.
nativeReadinessMaxResponseBytes Integer 131072 Maximum number of bytes read from one association file. The server also enforces a fixed upper bound, so a mis-set value cannot remove the limit.
nativeReadinessTimeoutMs Integer 3000 Overall deadline in milliseconds for fetching one association file. The server also enforces a fixed upper bound.
nativeReadinessCacheTtlSeconds Integer 3600 Time in seconds a readiness result is served from cache before the files are fetched again.

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, or the app's App ID prefix when that differs (older apps)
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.

To check that the assetlinks.json and apple-app-site-association files an RP has deployed match these entries, see Native App Readiness.

Exporting the association files#

The server generates both files from these fields, for you to deploy on the RP-ID host. They are the same files the readiness check compares a deployed copy against.

Request File
GET /jans-fido2/restv1/rp/{rpConfigId}/association/assetlinks.json Android assetlinks.json, one statement per androidApps entry
GET /jans-fido2/restv1/rp/{rpConfigId}/association/apple-app-site-association Apple file, one TeamID.BundleID per complete iosApps entry

{rpConfigId} is the RP id as configured, URL-encoded. The answer is a JSON download, 404 when the RP is not configured or has no app of that platform. Nothing is served from Jans: both Apple and Android fetch https://<rp-id>/.well-known/... without following redirects, so deploy each file on the RP-ID host itself. Apple's file is named apple-app-site-association, with no .json extension.

Merge the exported entries into any file that is already there; do not replace it. The exports describe only the passkey (webcredentials and get_login_creds) entries. A host that already serves these files may also use them for universal links or Android App Links, such as applinks in the Apple file or other statements in assetlinks.json, and overwriting the file removes those. Add the exported statements to the Android array, and the exported app IDs to webcredentials.apps, and leave everything else as it is.

Check that teamId is the app's App ID prefix. Apple expects <App ID prefix>.<bundle ID> in webcredentials.apps. For most apps the prefix is the Team ID, but for older apps it can be a different value. Use the prefix shown for the app's App ID under Identifiers in your Apple Developer account (it is also the part before the bundle ID in the app's application-identifier entitlement), and record that value as teamId.

After deploying, run the native app readiness check for the same RP (POST /jans-fido2/restv1/rp/{rpConfigId}/readiness, which needs nativeReadinessEnabled and nativeReadinessAllowExternalFetch) to confirm the deployed files match the configured apps. A mismatched fingerprint or Team ID is the most common deployment failure, and the check reports it.

Native app readiness#

The nativeReadiness* properties control a check that compares the assetlinks.json and apple-app-site-association files an RP has deployed with the androidApps and iosApps recorded under rp (see above). Everything is off by default, so an upgrade changes nothing until an administrator turns it on.

  • nativeReadinessEnabled is the master switch. While it is false the endpoint does no work.
  • nativeReadinessAllowExternalFetch is a second, separate opt-in. The server only contacts an RP's /.well-known files when both flags are true; with only nativeReadinessEnabled set it reports what is configured and does not fetch.
  • nativeReadinessMaxResponseBytes (default 128 KiB) and nativeReadinessTimeoutMs (default 3 seconds) bound each fetch. The server also applies a fixed upper limit to both (256 KiB and 10 seconds), so a mistaken value cannot make a fetch unbounded. A value of zero or less falls back to the default. The time limit covers the HTTP fetch only; the name lookup happens before it starts and is bounded only by the server's DNS resolver.
  • nativeReadinessCacheTtlSeconds is how long a result is reused before the files are fetched again. It is never shorter than 60 seconds.

The check is run with POST /jans-fido2/restv1/rp/{rpConfigId}/readiness, where rpConfigId is the RP id exactly as configured, URL-encoded. The body is empty or {"recheck": true}, which skips the cached result. The endpoint cannot be pointed at any other host: only the stored RP ID is ever fetched, and it must be a public host name. Like the other diagnostic endpoints, it relies on the infrastructure or the Config API plugin for authentication.

The response lists, for android and ios, each check with a status of pass, warn or fail and a short reason, together with checkedAt, fromCache and nextRecheckAllowedAt. It never contains the content of the fetched files. A request that would exceed a rate limit gets HTTP 429 with a Retry-After header and causes no fetch. The limits apply per server node: one fetch per RP per minute, 20 files per minute in total, and 3 checks at a time. Each executed check is written to the server log as a native-readiness-audit line.

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.