diff --git a/_generated-doc/main/config/quarkus-all-config.adoc b/_generated-doc/main/config/quarkus-all-config.adoc index 2f498368ad9..cba56674714 100644 --- a/_generated-doc/main/config/quarkus-all-config.adoc +++ b/_generated-doc/main/config/quarkus-all-config.adoc @@ -9950,6 +9950,27 @@ endif::add-copy-button-to-env-var[] |boolean |`false` +a|icon:lock[title=Fixed at build time] [[quarkus-core_quarkus-test-class-orderer]] [.property-path]##link:#quarkus-core_quarkus-test-class-orderer[`quarkus.test.class-orderer`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.test.class-orderer+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +The FQCN of the JUnit `ClassOrderer` to use. If the class cannot be found, it fallbacks to JUnit default behaviour which does not set a `ClassOrderer` at all. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_TEST_CLASS_ORDERER+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_TEST_CLASS_ORDERER+++` +endif::add-copy-button-to-env-var[] +-- +|string +|`io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer` + a|icon:lock[title=Fixed at build time] [[quarkus-core_quarkus-test-include-tags]] [.property-path]##link:#quarkus-core_quarkus-test-include-tags[`quarkus.test.include-tags`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.test.include-tags+++[] @@ -84848,26 +84869,26 @@ endif::add-copy-button-to-env-var[] |boolean |`true` -a| [[quarkus-security-webauthn_quarkus-webauthn-origin]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-origin[`quarkus.webauthn.origin`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-origins]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-origins[`quarkus.webauthn.origins`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.origin+++[] +config_property_copy_button:+++quarkus.webauthn.origins+++[] endif::add-copy-button-to-config-props[] [.description] -- -The origin of the application. The origin is basically protocol, host and port. If you are calling WebAuthn API while your application is located at `https://example.com/login`, then origin will be `https://example.com`. If you are calling from `http://localhost:2823/test`, then the origin will be `http://localhost:2823`. Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost, which is considered secure context. +The origins of the application. The origin is basically protocol, host and port. If you are calling WebAuthn API while your application is located at `https://example.com/login`, then origin will be `https://example.com`. If you are calling from `http://localhost:2823/test`, then the origin will be `http://localhost:2823`. Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost, which is considered secure context. If unspecified, this defaults to whatever URI this application is deployed on. This allows more than one value if you want to allow multiple origins. See https://w3c.github.io/webauthn/++#++sctn-related-origins ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ORIGIN+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ORIGINS+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_ORIGIN+++` +Environment variable: `+++QUARKUS_WEBAUTHN_ORIGINS+++` endif::add-copy-button-to-env-var[] -- -|string -| +|list of string +|`The URI this application is deployed on` a| [[quarkus-security-webauthn_quarkus-webauthn-transports]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-transports[`quarkus.webauthn.transports`]## ifdef::add-copy-button-to-config-props[] @@ -84903,7 +84924,7 @@ endif::add-copy-button-to-config-props[] [.description] -- -The id (or domain name of your server) +The id (or domain name of your server, as obtained from the first entry of `origins` or looking at where this request is being served from) ifdef::add-copy-button-to-env-var[] @@ -84914,7 +84935,7 @@ Environment variable: `+++QUARKUS_WEBAUTHN_RELYING_PARTY_ID+++` endif::add-copy-button-to-env-var[] -- |string -| +|`The host name of the first allowed origin, or the host where this application is deployed` a| [[quarkus-security-webauthn_quarkus-webauthn-relying-party-name]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-relying-party-name[`quarkus.webauthn.relying-party.name`]## ifdef::add-copy-button-to-config-props[] @@ -84963,27 +84984,48 @@ endif::add-copy-button-to-env-var[] a|`platform`, `cross-platform` | -a| [[quarkus-security-webauthn_quarkus-webauthn-require-resident-key]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-require-resident-key[`quarkus.webauthn.require-resident-key`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-load-metadata]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-load-metadata[`quarkus.webauthn.load-metadata`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.require-resident-key+++[] +config_property_copy_button:+++quarkus.webauthn.load-metadata+++[] endif::add-copy-button-to-config-props[] [.description] -- -Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this means that you cannot reuse the authenticator to log into a second computer. +Load the FIDO metadata for verification. See https://fidoalliance.org/metadata/. Only useful for attestations different from `Attestation.NONE`. ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_REQUIRE_RESIDENT_KEY+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_LOAD_METADATA+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_REQUIRE_RESIDENT_KEY+++` +Environment variable: `+++QUARKUS_WEBAUTHN_LOAD_METADATA+++` endif::add-copy-button-to-env-var[] -- |boolean |`false` +a| [[quarkus-security-webauthn_quarkus-webauthn-resident-key]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-resident-key[`quarkus.webauthn.resident-key`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.resident-key+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this means that you cannot reuse the authenticator to log into a second computer. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_RESIDENT_KEY+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_RESIDENT_KEY+++` +endif::add-copy-button-to-env-var[] +-- +a|`discouraged`, `preferred`, `required` +|`REQUIRED` + a| [[quarkus-security-webauthn_quarkus-webauthn-user-verification]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-user-verification[`quarkus.webauthn.user-verification`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.webauthn.user-verification+++[] @@ -85007,7 +85049,28 @@ Environment variable: `+++QUARKUS_WEBAUTHN_USER_VERIFICATION+++` endif::add-copy-button-to-env-var[] -- a|`required`, `preferred`, `discouraged` -|`DISCOURAGED` +|`REQUIRED` + +a| [[quarkus-security-webauthn_quarkus-webauthn-user-presence-required]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-user-presence-required[`quarkus.webauthn.user-presence-required`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.user-presence-required+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +User presence requirements. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_USER_PRESENCE_REQUIRED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_USER_PRESENCE_REQUIRED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` a| [[quarkus-security-webauthn_quarkus-webauthn-timeout]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-timeout[`quarkus.webauthn.timeout`]## ifdef::add-copy-button-to-config-props[] @@ -85028,7 +85091,7 @@ Environment variable: `+++QUARKUS_WEBAUTHN_TIMEOUT+++` endif::add-copy-button-to-env-var[] -- |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-all-config[icon:question-circle[title=More information about the Duration format]] -|`60s` +|`5m` a| [[quarkus-security-webauthn_quarkus-webauthn-attestation]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-attestation[`quarkus.webauthn.attestation`]## ifdef::add-copy-button-to-config-props[] @@ -85056,22 +85119,22 @@ endif::add-copy-button-to-env-var[] a|`none`, `indirect`, `direct`, `enterprise` |`NONE` -a| [[quarkus-security-webauthn_quarkus-webauthn-pub-key-cred-params]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-pub-key-cred-params[`quarkus.webauthn.pub-key-cred-params`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-public-key-credential-parameters]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-public-key-credential-parameters[`quarkus.webauthn.public-key-credential-parameters`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.pub-key-cred-params+++[] +config_property_copy_button:+++quarkus.webauthn.public-key-credential-parameters+++[] endif::add-copy-button-to-config-props[] [.description] -- -Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must support at least the following 2 algorithms: `ES256` and `RS256`. Applications may require stronger keys and algorithms, for example: `ES512` or `EdDSA`. Note that the use of stronger algorithms, e.g.: `EdDSA` may require Java 15 or a cryptographic `JCE` provider that implements the algorithms. +Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must support at least the following 2 algorithms: `ES256` and `RS256`. Applications may require stronger keys and algorithms, for example: `ES512` or `EdDSA`. Note that the use of stronger algorithms, e.g.: `EdDSA` may require Java 15 or a cryptographic `JCE` provider that implements the algorithms. See https://www.w3.org/TR/webauthn-1/++#++dictdef-publickeycredentialparameters ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_PUB_KEY_CRED_PARAMS+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_PARAMETERS+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_PUB_KEY_CRED_PARAMS+++` +Environment variable: `+++QUARKUS_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_PARAMETERS+++` endif::add-copy-button-to-env-var[] -- a|list of `es256`, `es384`, `es512`, `ps256`, `ps384`, `ps512`, `es256k`, `rs256`, `rs384`, `rs512`, `rs1`, `ed-dsa` @@ -85203,27 +85266,6 @@ endif::add-copy-button-to-env-var[] |string |`_quarkus_webauthn_challenge` -a| [[quarkus-security-webauthn_quarkus-webauthn-challenge-username-cookie-name]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-challenge-username-cookie-name[`quarkus.webauthn.challenge-username-cookie-name`]## -ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.challenge-username-cookie-name+++[] -endif::add-copy-button-to-config-props[] - - -[.description] --- -The cookie that is used to store the username data during login/registration - - -ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_CHALLENGE_USERNAME_COOKIE_NAME+++[] -endif::add-copy-button-to-env-var[] -ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_CHALLENGE_USERNAME_COOKIE_NAME+++` -endif::add-copy-button-to-env-var[] --- -|string -|`_quarkus_webauthn_username` - a| [[quarkus-security-webauthn_quarkus-webauthn-cookie-same-site]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-cookie-same-site[`quarkus.webauthn.cookie-same-site`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.webauthn.cookie-same-site+++[] @@ -85287,6 +85329,48 @@ endif::add-copy-button-to-env-var[] |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-all-config[icon:question-circle[title=More information about the Duration format]] | +a| [[quarkus-security-webauthn_quarkus-webauthn-enable-registration-endpoint]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-enable-registration-endpoint[`quarkus.webauthn.enable-registration-endpoint`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.enable-registration-endpoint+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Set to `true` if you want to enable the default registration endpoint at `/q/webauthn/register`, in which case you should also implement the `WebAuthnUserProvider.store` method. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ENABLE_REGISTRATION_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_ENABLE_REGISTRATION_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a| [[quarkus-security-webauthn_quarkus-webauthn-enable-login-endpoint]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-enable-login-endpoint[`quarkus.webauthn.enable-login-endpoint`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.enable-login-endpoint+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Set to `true` if you want to enable the default login endpoint at `/q/webauthn/login`, in which case you should also implement the `WebAuthnUserProvider.update` method. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ENABLE_LOGIN_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_ENABLE_LOGIN_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + h|[.extension-name]##SmallRye Fault Tolerance## h|Type diff --git a/_generated-doc/main/config/quarkus-core_quarkus.test.adoc b/_generated-doc/main/config/quarkus-core_quarkus.test.adoc index e7ed91bff62..94924f61c8a 100644 --- a/_generated-doc/main/config/quarkus-core_quarkus.test.adoc +++ b/_generated-doc/main/config/quarkus-core_quarkus.test.adoc @@ -55,6 +55,27 @@ endif::add-copy-button-to-env-var[] |boolean |`false` +a|icon:lock[title=Fixed at build time] [[quarkus-core_quarkus-test-class-orderer]] [.property-path]##link:#quarkus-core_quarkus-test-class-orderer[`quarkus.test.class-orderer`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.test.class-orderer+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +The FQCN of the JUnit `ClassOrderer` to use. If the class cannot be found, it fallbacks to JUnit default behaviour which does not set a `ClassOrderer` at all. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_TEST_CLASS_ORDERER+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_TEST_CLASS_ORDERER+++` +endif::add-copy-button-to-env-var[] +-- +|string +|`io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer` + a|icon:lock[title=Fixed at build time] [[quarkus-core_quarkus-test-include-tags]] [.property-path]##link:#quarkus-core_quarkus-test-include-tags[`quarkus.test.include-tags`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.test.include-tags+++[] diff --git a/_generated-doc/main/config/quarkus-security-webauthn.adoc b/_generated-doc/main/config/quarkus-security-webauthn.adoc index e849ec5b732..51173f93fc5 100644 --- a/_generated-doc/main/config/quarkus-security-webauthn.adoc +++ b/_generated-doc/main/config/quarkus-security-webauthn.adoc @@ -28,26 +28,26 @@ endif::add-copy-button-to-env-var[] |boolean |`true` -a| [[quarkus-security-webauthn_quarkus-webauthn-origin]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-origin[`quarkus.webauthn.origin`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-origins]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-origins[`quarkus.webauthn.origins`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.origin+++[] +config_property_copy_button:+++quarkus.webauthn.origins+++[] endif::add-copy-button-to-config-props[] [.description] -- -The origin of the application. The origin is basically protocol, host and port. If you are calling WebAuthn API while your application is located at `https://example.com/login`, then origin will be `https://example.com`. If you are calling from `http://localhost:2823/test`, then the origin will be `http://localhost:2823`. Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost, which is considered secure context. +The origins of the application. The origin is basically protocol, host and port. If you are calling WebAuthn API while your application is located at `https://example.com/login`, then origin will be `https://example.com`. If you are calling from `http://localhost:2823/test`, then the origin will be `http://localhost:2823`. Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost, which is considered secure context. If unspecified, this defaults to whatever URI this application is deployed on. This allows more than one value if you want to allow multiple origins. See https://w3c.github.io/webauthn/++#++sctn-related-origins ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ORIGIN+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ORIGINS+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_ORIGIN+++` +Environment variable: `+++QUARKUS_WEBAUTHN_ORIGINS+++` endif::add-copy-button-to-env-var[] -- -|string -| +|list of string +|`The URI this application is deployed on` a| [[quarkus-security-webauthn_quarkus-webauthn-transports]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-transports[`quarkus.webauthn.transports`]## ifdef::add-copy-button-to-config-props[] @@ -83,7 +83,7 @@ endif::add-copy-button-to-config-props[] [.description] -- -The id (or domain name of your server) +The id (or domain name of your server, as obtained from the first entry of `origins` or looking at where this request is being served from) ifdef::add-copy-button-to-env-var[] @@ -94,7 +94,7 @@ Environment variable: `+++QUARKUS_WEBAUTHN_RELYING_PARTY_ID+++` endif::add-copy-button-to-env-var[] -- |string -| +|`The host name of the first allowed origin, or the host where this application is deployed` a| [[quarkus-security-webauthn_quarkus-webauthn-relying-party-name]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-relying-party-name[`quarkus.webauthn.relying-party.name`]## ifdef::add-copy-button-to-config-props[] @@ -143,27 +143,48 @@ endif::add-copy-button-to-env-var[] a|`platform`, `cross-platform` | -a| [[quarkus-security-webauthn_quarkus-webauthn-require-resident-key]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-require-resident-key[`quarkus.webauthn.require-resident-key`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-load-metadata]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-load-metadata[`quarkus.webauthn.load-metadata`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.require-resident-key+++[] +config_property_copy_button:+++quarkus.webauthn.load-metadata+++[] endif::add-copy-button-to-config-props[] [.description] -- -Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this means that you cannot reuse the authenticator to log into a second computer. +Load the FIDO metadata for verification. See https://fidoalliance.org/metadata/. Only useful for attestations different from `Attestation.NONE`. ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_REQUIRE_RESIDENT_KEY+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_LOAD_METADATA+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_REQUIRE_RESIDENT_KEY+++` +Environment variable: `+++QUARKUS_WEBAUTHN_LOAD_METADATA+++` endif::add-copy-button-to-env-var[] -- |boolean |`false` +a| [[quarkus-security-webauthn_quarkus-webauthn-resident-key]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-resident-key[`quarkus.webauthn.resident-key`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.resident-key+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this means that you cannot reuse the authenticator to log into a second computer. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_RESIDENT_KEY+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_RESIDENT_KEY+++` +endif::add-copy-button-to-env-var[] +-- +a|`discouraged`, `preferred`, `required` +|`REQUIRED` + a| [[quarkus-security-webauthn_quarkus-webauthn-user-verification]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-user-verification[`quarkus.webauthn.user-verification`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.webauthn.user-verification+++[] @@ -187,7 +208,28 @@ Environment variable: `+++QUARKUS_WEBAUTHN_USER_VERIFICATION+++` endif::add-copy-button-to-env-var[] -- a|`required`, `preferred`, `discouraged` -|`DISCOURAGED` +|`REQUIRED` + +a| [[quarkus-security-webauthn_quarkus-webauthn-user-presence-required]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-user-presence-required[`quarkus.webauthn.user-presence-required`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.user-presence-required+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +User presence requirements. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_USER_PRESENCE_REQUIRED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_USER_PRESENCE_REQUIRED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` a| [[quarkus-security-webauthn_quarkus-webauthn-timeout]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-timeout[`quarkus.webauthn.timeout`]## ifdef::add-copy-button-to-config-props[] @@ -208,7 +250,7 @@ Environment variable: `+++QUARKUS_WEBAUTHN_TIMEOUT+++` endif::add-copy-button-to-env-var[] -- |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-security-webauthn_quarkus-webauthn[icon:question-circle[title=More information about the Duration format]] -|`60s` +|`5m` a| [[quarkus-security-webauthn_quarkus-webauthn-attestation]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-attestation[`quarkus.webauthn.attestation`]## ifdef::add-copy-button-to-config-props[] @@ -236,22 +278,22 @@ endif::add-copy-button-to-env-var[] a|`none`, `indirect`, `direct`, `enterprise` |`NONE` -a| [[quarkus-security-webauthn_quarkus-webauthn-pub-key-cred-params]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-pub-key-cred-params[`quarkus.webauthn.pub-key-cred-params`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-public-key-credential-parameters]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-public-key-credential-parameters[`quarkus.webauthn.public-key-credential-parameters`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.pub-key-cred-params+++[] +config_property_copy_button:+++quarkus.webauthn.public-key-credential-parameters+++[] endif::add-copy-button-to-config-props[] [.description] -- -Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must support at least the following 2 algorithms: `ES256` and `RS256`. Applications may require stronger keys and algorithms, for example: `ES512` or `EdDSA`. Note that the use of stronger algorithms, e.g.: `EdDSA` may require Java 15 or a cryptographic `JCE` provider that implements the algorithms. +Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must support at least the following 2 algorithms: `ES256` and `RS256`. Applications may require stronger keys and algorithms, for example: `ES512` or `EdDSA`. Note that the use of stronger algorithms, e.g.: `EdDSA` may require Java 15 or a cryptographic `JCE` provider that implements the algorithms. See https://www.w3.org/TR/webauthn-1/++#++dictdef-publickeycredentialparameters ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_PUB_KEY_CRED_PARAMS+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_PARAMETERS+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_PUB_KEY_CRED_PARAMS+++` +Environment variable: `+++QUARKUS_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_PARAMETERS+++` endif::add-copy-button-to-env-var[] -- a|list of `es256`, `es384`, `es512`, `ps256`, `ps384`, `ps512`, `es256k`, `rs256`, `rs384`, `rs512`, `rs1`, `ed-dsa` @@ -383,27 +425,6 @@ endif::add-copy-button-to-env-var[] |string |`_quarkus_webauthn_challenge` -a| [[quarkus-security-webauthn_quarkus-webauthn-challenge-username-cookie-name]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-challenge-username-cookie-name[`quarkus.webauthn.challenge-username-cookie-name`]## -ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.challenge-username-cookie-name+++[] -endif::add-copy-button-to-config-props[] - - -[.description] --- -The cookie that is used to store the username data during login/registration - - -ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_CHALLENGE_USERNAME_COOKIE_NAME+++[] -endif::add-copy-button-to-env-var[] -ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_CHALLENGE_USERNAME_COOKIE_NAME+++` -endif::add-copy-button-to-env-var[] --- -|string -|`_quarkus_webauthn_username` - a| [[quarkus-security-webauthn_quarkus-webauthn-cookie-same-site]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-cookie-same-site[`quarkus.webauthn.cookie-same-site`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.webauthn.cookie-same-site+++[] @@ -467,6 +488,48 @@ endif::add-copy-button-to-env-var[] |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-security-webauthn_quarkus-webauthn[icon:question-circle[title=More information about the Duration format]] | +a| [[quarkus-security-webauthn_quarkus-webauthn-enable-registration-endpoint]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-enable-registration-endpoint[`quarkus.webauthn.enable-registration-endpoint`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.enable-registration-endpoint+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Set to `true` if you want to enable the default registration endpoint at `/q/webauthn/register`, in which case you should also implement the `WebAuthnUserProvider.store` method. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ENABLE_REGISTRATION_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_ENABLE_REGISTRATION_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a| [[quarkus-security-webauthn_quarkus-webauthn-enable-login-endpoint]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-enable-login-endpoint[`quarkus.webauthn.enable-login-endpoint`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.enable-login-endpoint+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Set to `true` if you want to enable the default login endpoint at `/q/webauthn/login`, in which case you should also implement the `WebAuthnUserProvider.update` method. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ENABLE_LOGIN_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_ENABLE_LOGIN_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + |=== ifndef::no-duration-note[] diff --git a/_generated-doc/main/config/quarkus-security-webauthn_quarkus.webauthn.adoc b/_generated-doc/main/config/quarkus-security-webauthn_quarkus.webauthn.adoc index e849ec5b732..51173f93fc5 100644 --- a/_generated-doc/main/config/quarkus-security-webauthn_quarkus.webauthn.adoc +++ b/_generated-doc/main/config/quarkus-security-webauthn_quarkus.webauthn.adoc @@ -28,26 +28,26 @@ endif::add-copy-button-to-env-var[] |boolean |`true` -a| [[quarkus-security-webauthn_quarkus-webauthn-origin]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-origin[`quarkus.webauthn.origin`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-origins]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-origins[`quarkus.webauthn.origins`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.origin+++[] +config_property_copy_button:+++quarkus.webauthn.origins+++[] endif::add-copy-button-to-config-props[] [.description] -- -The origin of the application. The origin is basically protocol, host and port. If you are calling WebAuthn API while your application is located at `https://example.com/login`, then origin will be `https://example.com`. If you are calling from `http://localhost:2823/test`, then the origin will be `http://localhost:2823`. Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost, which is considered secure context. +The origins of the application. The origin is basically protocol, host and port. If you are calling WebAuthn API while your application is located at `https://example.com/login`, then origin will be `https://example.com`. If you are calling from `http://localhost:2823/test`, then the origin will be `http://localhost:2823`. Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost, which is considered secure context. If unspecified, this defaults to whatever URI this application is deployed on. This allows more than one value if you want to allow multiple origins. See https://w3c.github.io/webauthn/++#++sctn-related-origins ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ORIGIN+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ORIGINS+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_ORIGIN+++` +Environment variable: `+++QUARKUS_WEBAUTHN_ORIGINS+++` endif::add-copy-button-to-env-var[] -- -|string -| +|list of string +|`The URI this application is deployed on` a| [[quarkus-security-webauthn_quarkus-webauthn-transports]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-transports[`quarkus.webauthn.transports`]## ifdef::add-copy-button-to-config-props[] @@ -83,7 +83,7 @@ endif::add-copy-button-to-config-props[] [.description] -- -The id (or domain name of your server) +The id (or domain name of your server, as obtained from the first entry of `origins` or looking at where this request is being served from) ifdef::add-copy-button-to-env-var[] @@ -94,7 +94,7 @@ Environment variable: `+++QUARKUS_WEBAUTHN_RELYING_PARTY_ID+++` endif::add-copy-button-to-env-var[] -- |string -| +|`The host name of the first allowed origin, or the host where this application is deployed` a| [[quarkus-security-webauthn_quarkus-webauthn-relying-party-name]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-relying-party-name[`quarkus.webauthn.relying-party.name`]## ifdef::add-copy-button-to-config-props[] @@ -143,27 +143,48 @@ endif::add-copy-button-to-env-var[] a|`platform`, `cross-platform` | -a| [[quarkus-security-webauthn_quarkus-webauthn-require-resident-key]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-require-resident-key[`quarkus.webauthn.require-resident-key`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-load-metadata]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-load-metadata[`quarkus.webauthn.load-metadata`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.require-resident-key+++[] +config_property_copy_button:+++quarkus.webauthn.load-metadata+++[] endif::add-copy-button-to-config-props[] [.description] -- -Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this means that you cannot reuse the authenticator to log into a second computer. +Load the FIDO metadata for verification. See https://fidoalliance.org/metadata/. Only useful for attestations different from `Attestation.NONE`. ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_REQUIRE_RESIDENT_KEY+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_LOAD_METADATA+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_REQUIRE_RESIDENT_KEY+++` +Environment variable: `+++QUARKUS_WEBAUTHN_LOAD_METADATA+++` endif::add-copy-button-to-env-var[] -- |boolean |`false` +a| [[quarkus-security-webauthn_quarkus-webauthn-resident-key]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-resident-key[`quarkus.webauthn.resident-key`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.resident-key+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this means that you cannot reuse the authenticator to log into a second computer. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_RESIDENT_KEY+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_RESIDENT_KEY+++` +endif::add-copy-button-to-env-var[] +-- +a|`discouraged`, `preferred`, `required` +|`REQUIRED` + a| [[quarkus-security-webauthn_quarkus-webauthn-user-verification]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-user-verification[`quarkus.webauthn.user-verification`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.webauthn.user-verification+++[] @@ -187,7 +208,28 @@ Environment variable: `+++QUARKUS_WEBAUTHN_USER_VERIFICATION+++` endif::add-copy-button-to-env-var[] -- a|`required`, `preferred`, `discouraged` -|`DISCOURAGED` +|`REQUIRED` + +a| [[quarkus-security-webauthn_quarkus-webauthn-user-presence-required]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-user-presence-required[`quarkus.webauthn.user-presence-required`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.user-presence-required+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +User presence requirements. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_USER_PRESENCE_REQUIRED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_USER_PRESENCE_REQUIRED+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`true` a| [[quarkus-security-webauthn_quarkus-webauthn-timeout]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-timeout[`quarkus.webauthn.timeout`]## ifdef::add-copy-button-to-config-props[] @@ -208,7 +250,7 @@ Environment variable: `+++QUARKUS_WEBAUTHN_TIMEOUT+++` endif::add-copy-button-to-env-var[] -- |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-security-webauthn_quarkus-webauthn[icon:question-circle[title=More information about the Duration format]] -|`60s` +|`5m` a| [[quarkus-security-webauthn_quarkus-webauthn-attestation]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-attestation[`quarkus.webauthn.attestation`]## ifdef::add-copy-button-to-config-props[] @@ -236,22 +278,22 @@ endif::add-copy-button-to-env-var[] a|`none`, `indirect`, `direct`, `enterprise` |`NONE` -a| [[quarkus-security-webauthn_quarkus-webauthn-pub-key-cred-params]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-pub-key-cred-params[`quarkus.webauthn.pub-key-cred-params`]## +a| [[quarkus-security-webauthn_quarkus-webauthn-public-key-credential-parameters]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-public-key-credential-parameters[`quarkus.webauthn.public-key-credential-parameters`]## ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.pub-key-cred-params+++[] +config_property_copy_button:+++quarkus.webauthn.public-key-credential-parameters+++[] endif::add-copy-button-to-config-props[] [.description] -- -Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must support at least the following 2 algorithms: `ES256` and `RS256`. Applications may require stronger keys and algorithms, for example: `ES512` or `EdDSA`. Note that the use of stronger algorithms, e.g.: `EdDSA` may require Java 15 or a cryptographic `JCE` provider that implements the algorithms. +Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must support at least the following 2 algorithms: `ES256` and `RS256`. Applications may require stronger keys and algorithms, for example: `ES512` or `EdDSA`. Note that the use of stronger algorithms, e.g.: `EdDSA` may require Java 15 or a cryptographic `JCE` provider that implements the algorithms. See https://www.w3.org/TR/webauthn-1/++#++dictdef-publickeycredentialparameters ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_PUB_KEY_CRED_PARAMS+++[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_PARAMETERS+++[] endif::add-copy-button-to-env-var[] ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_PUB_KEY_CRED_PARAMS+++` +Environment variable: `+++QUARKUS_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_PARAMETERS+++` endif::add-copy-button-to-env-var[] -- a|list of `es256`, `es384`, `es512`, `ps256`, `ps384`, `ps512`, `es256k`, `rs256`, `rs384`, `rs512`, `rs1`, `ed-dsa` @@ -383,27 +425,6 @@ endif::add-copy-button-to-env-var[] |string |`_quarkus_webauthn_challenge` -a| [[quarkus-security-webauthn_quarkus-webauthn-challenge-username-cookie-name]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-challenge-username-cookie-name[`quarkus.webauthn.challenge-username-cookie-name`]## -ifdef::add-copy-button-to-config-props[] -config_property_copy_button:+++quarkus.webauthn.challenge-username-cookie-name+++[] -endif::add-copy-button-to-config-props[] - - -[.description] --- -The cookie that is used to store the username data during login/registration - - -ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_CHALLENGE_USERNAME_COOKIE_NAME+++[] -endif::add-copy-button-to-env-var[] -ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_WEBAUTHN_CHALLENGE_USERNAME_COOKIE_NAME+++` -endif::add-copy-button-to-env-var[] --- -|string -|`_quarkus_webauthn_username` - a| [[quarkus-security-webauthn_quarkus-webauthn-cookie-same-site]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-cookie-same-site[`quarkus.webauthn.cookie-same-site`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.webauthn.cookie-same-site+++[] @@ -467,6 +488,48 @@ endif::add-copy-button-to-env-var[] |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-security-webauthn_quarkus-webauthn[icon:question-circle[title=More information about the Duration format]] | +a| [[quarkus-security-webauthn_quarkus-webauthn-enable-registration-endpoint]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-enable-registration-endpoint[`quarkus.webauthn.enable-registration-endpoint`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.enable-registration-endpoint+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Set to `true` if you want to enable the default registration endpoint at `/q/webauthn/register`, in which case you should also implement the `WebAuthnUserProvider.store` method. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ENABLE_REGISTRATION_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_ENABLE_REGISTRATION_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + +a| [[quarkus-security-webauthn_quarkus-webauthn-enable-login-endpoint]] [.property-path]##link:#quarkus-security-webauthn_quarkus-webauthn-enable-login-endpoint[`quarkus.webauthn.enable-login-endpoint`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.webauthn.enable-login-endpoint+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Set to `true` if you want to enable the default login endpoint at `/q/webauthn/login`, in which case you should also implement the `WebAuthnUserProvider.update` method. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_WEBAUTHN_ENABLE_LOGIN_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_WEBAUTHN_ENABLE_LOGIN_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + |=== ifndef::no-duration-note[] diff --git a/_versions/main/guides/getting-started-testing.adoc b/_versions/main/guides/getting-started-testing.adoc index 916b16f57fd..e2c0fac89cc 100644 --- a/_versions/main/guides/getting-started-testing.adoc +++ b/_versions/main/guides/getting-started-testing.adoc @@ -472,24 +472,10 @@ a bit slower, as it adds a shutdown/startup cycle to the test time, but gives a To reduce the amount of times Quarkus needs to restart, `io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer` is registered as a global `ClassOrderer` as described in the link:https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order-classes[JUnit 5 User Guide]. -The behavior of this `ClassOrderer` is configurable via `junit-platform.properties` (see the source code or javadoc for more details). -It can also be disabled entirely by setting another `ClassOrderer` that is provided by JUnit 5 or even your own custom one. + -Please note that as of JUnit 5.8.2 link:https://github.com/junit-team/junit5/issues/2794[only a single `junit-platform.properties` is picked up and a warning is logged if more than one is found]. -If you encounter such warnings, you can get rid of them by removing the Quarkus-supplied `junit-platform.properties` from the classpath via an exclusion: -[source,xml] ----- - - io.quarkus - quarkus-junit5 - test - - - io.quarkus - quarkus-junit5-properties - - - ----- +The behavior of this `ClassOrderer` is configurable via `application.properties` using the property +`quarkus.test.class-orderer`. The property accepts the FQCN of the `ClassOrderer` to use. If the class cannot be found, +it fallbacks to JUnit default behaviour which does not set a `ClassOrderer` at all. It can also be disabled entirely by +setting another `ClassOrderer` that is provided by JUnit 5 or even your own custom one. === Writing a Profile diff --git a/_versions/main/guides/images/webauthn-1.png b/_versions/main/guides/images/webauthn-1.png index 70b1764e343..515e71df96b 100644 Binary files a/_versions/main/guides/images/webauthn-1.png and b/_versions/main/guides/images/webauthn-1.png differ diff --git a/_versions/main/guides/images/webauthn-2.png b/_versions/main/guides/images/webauthn-2.png index 760faf4a615..e9522798b01 100644 Binary files a/_versions/main/guides/images/webauthn-2.png and b/_versions/main/guides/images/webauthn-2.png differ diff --git a/_versions/main/guides/images/webauthn-4.png b/_versions/main/guides/images/webauthn-4.png index 2da3b1d5a17..934a175c7bd 100644 Binary files a/_versions/main/guides/images/webauthn-4.png and b/_versions/main/guides/images/webauthn-4.png differ diff --git a/_versions/main/guides/images/webauthn-5.png b/_versions/main/guides/images/webauthn-5.png index 042d943a3fb..19860f16d5b 100644 Binary files a/_versions/main/guides/images/webauthn-5.png and b/_versions/main/guides/images/webauthn-5.png differ diff --git a/_versions/main/guides/images/webauthn-custom-login.svg b/_versions/main/guides/images/webauthn-custom-login.svg new file mode 100644 index 00000000000..e08ba0cd89e --- /dev/null +++ b/_versions/main/guides/images/webauthn-custom-login.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_versions/main/guides/images/webauthn-custom-register.svg b/_versions/main/guides/images/webauthn-custom-register.svg new file mode 100644 index 00000000000..75b98727ac9 --- /dev/null +++ b/_versions/main/guides/images/webauthn-custom-register.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_versions/main/guides/images/webauthn-login.svg b/_versions/main/guides/images/webauthn-login.svg new file mode 100644 index 00000000000..0055a442c28 --- /dev/null +++ b/_versions/main/guides/images/webauthn-login.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_versions/main/guides/images/webauthn-register.svg b/_versions/main/guides/images/webauthn-register.svg new file mode 100644 index 00000000000..5c60cdb486b --- /dev/null +++ b/_versions/main/guides/images/webauthn-register.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/_versions/main/guides/rest-virtual-threads.adoc b/_versions/main/guides/rest-virtual-threads.adoc index 6af60d67b49..9de9a9bf4c7 100644 --- a/_versions/main/guides/rest-virtual-threads.adoc +++ b/_versions/main/guides/rest-virtual-threads.adoc @@ -53,7 +53,7 @@ and in particular, adds the following dependencies: .build.gradle ---- implementation("io.quarkus:quarkus-rest-jackson") -implementation("quarkus-rest-client-jackson") +implementation("io.quarkus:quarkus-rest-client-jackson") ---- [NOTE] diff --git a/_versions/main/guides/security-webauthn.adoc b/_versions/main/guides/security-webauthn.adoc index 996691c7a81..09ee4ce6b24 100644 --- a/_versions/main/guides/security-webauthn.adoc +++ b/_versions/main/guides/security-webauthn.adoc @@ -10,6 +10,8 @@ include::_attributes.adoc[] :categories: security :topics: security,webauthn,authorization :extensions: io.quarkus:quarkus-security-webauthn +:webauthn-api: https://javadoc.io/doc/io.quarkus/quarkus-security-webauthn/{quarkus-version} +:webauthn-test-api: https://javadoc.io/doc/io.quarkus/quarkus-test-security-webauthn/{quarkus-version} This guide demonstrates how your Quarkus application can use WebAuthn authentication instead of passwords. @@ -221,7 +223,7 @@ public class UserResource { === Storing our WebAuthn credentials -We can now describe how our WebAuthn credentials are stored in our database with three entities. Note that we've +We can now describe how our WebAuthn credentials are stored in our database with two entities. Note that we've simplified the model in order to only store one credential per user (who could actually have more than one WebAuthn credential and other data such as roles): @@ -229,139 +231,65 @@ and other data such as roles): ---- package org.acme.security.webauthn; -import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord.RequiredPersistedData; import jakarta.persistence.Entity; -import jakarta.persistence.OneToMany; +import jakarta.persistence.Id; import jakarta.persistence.OneToOne; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -import io.quarkus.hibernate.orm.panache.PanacheEntity; -import io.vertx.ext.auth.webauthn.Authenticator; -import io.vertx.ext.auth.webauthn.PublicKeyCredential; -@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"userName", "credID"})) @Entity -public class WebAuthnCredential extends PanacheEntity { - - /** - * The username linked to this authenticator - */ - public String userName; - - /** - * The type of key (must be "public-key") - */ - public String type = "public-key"; +public class WebAuthnCredential extends PanacheEntityBase { + + @Id + public String credentialId; - /** - * The non user identifiable id for the authenticator - */ - public String credID; - - /** - * The public key associated with this authenticator - */ - public String publicKey; - - /** - * The signature counter of the authenticator to prevent replay attacks - */ + public byte[] publicKey; + public long publicKeyAlgorithm; public long counter; - - public String aaguid; - - /** - * The Authenticator attestation certificates object, a JSON like: - *
{@code
-     *   {
-     *     "alg": "string",
-     *     "x5c": [
-     *       "base64"
-     *     ]
-     *   }
-     * }
- */ - /** - * The algorithm used for the public credential - */ - public PublicKeyCredential alg; - - /** - * The list of X509 certificates encoded as base64url. - */ - @OneToMany(mappedBy = "webAuthnCredential") - public List x5c = new ArrayList<>(); - - public String fmt; - - // owning side + public UUID aaguid; + + // this is the owning side @OneToOne public User user; public WebAuthnCredential() { } - - public WebAuthnCredential(Authenticator authenticator, User user) { - aaguid = authenticator.getAaguid(); - if(authenticator.getAttestationCertificates() != null) - alg = authenticator.getAttestationCertificates().getAlg(); - counter = authenticator.getCounter(); - credID = authenticator.getCredID(); - fmt = authenticator.getFmt(); - publicKey = authenticator.getPublicKey(); - type = authenticator.getType(); - userName = authenticator.getUserName(); - if(authenticator.getAttestationCertificates() != null - && authenticator.getAttestationCertificates().getX5c() != null) { - for (String x5c : authenticator.getAttestationCertificates().getX5c()) { - WebAuthnCertificate cert = new WebAuthnCertificate(); - cert.x5c = x5c; - cert.webAuthnCredential = this; - this.x5c.add(cert); - } - } + + public WebAuthnCredential(WebAuthnCredentialRecord credentialRecord, User user) { + RequiredPersistedData requiredPersistedData = + credentialRecord.getRequiredPersistedData(); + aaguid = requiredPersistedData.aaguid(); + counter = requiredPersistedData.counter(); + credentialId = requiredPersistedData.credentialId(); + publicKey = requiredPersistedData.publicKey(); + publicKeyAlgorithm = requiredPersistedData.publicKeyAlgorithm(); this.user = user; user.webAuthnCredential = this; } - public static List findByUserName(String userName) { - return list("userName", userName); + public WebAuthnCredentialRecord toWebAuthnCredentialRecord() { + return WebAuthnCredentialRecord + .fromRequiredPersistedData( + new RequiredPersistedData(user.userName, credentialId, + aaguid, publicKey, + publicKeyAlgorithm, counter)); } - public static List findByCredID(String credID) { - return list("credID", credID); + public static List findByUserName(String userName) { + return list("user.userName", userName); + } + + public static WebAuthnCredential findByCredentialId(String credentialId) { + return findById(credentialId); } } ---- -We also need a second entity for the credentials: - -[source,java] ----- -package org.acme.security.webauthn; - -import io.quarkus.hibernate.orm.panache.PanacheEntity; -import jakarta.persistence.Entity; -import jakarta.persistence.ManyToOne; - - -@Entity -public class WebAuthnCertificate extends PanacheEntity { - - @ManyToOne - public WebAuthnCredential webAuthnCredential; - - /** - * The list of X509 certificates encoded as base64url. - */ - public String x5c; -} ----- - -And last but not least, our user entity: +And our user entity: [source,java] ---- @@ -392,98 +320,74 @@ public class User extends PanacheEntity { ==== A note about usernames and credential IDs -WebAuthn relies on a combination of usernames (unique per user) and credential IDs (unique per authenticator device). - -The reasons why there are two such identifiers, and why they are not unique keys for the credentials themselves are: - -- A single user can have more than one authenticator device, which means a single username can map to multiple credential IDs, - all of which identify the same user. -- An authenticator device may be shared by multiple users, because a single person may want multiple user accounts with different - usernames, all of which having the same authenticator device. So a single credential ID may be used by multiple different users. +Usernames are unique and to your users. Every created WebAuthn credential record has a unique ID. -The combination of username and credential ID should be a unicity constraint for your credentials table, though. +You can allow (if you want, but you don't have to) your users to have more than one authenticator device, +which means a single username can map to multiple credential IDs, all of which identify the same user. === Exposing your entities to Quarkus WebAuthn -You need to define a bean implementing the `WebAuthnUserProvider` in order to allow the Quarkus WebAuthn +You need to define a bean implementing the link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`] in order to allow the Quarkus WebAuthn extension to load and store credentials. This is where you tell Quarkus how to turn your data model into the WebAuthn security model: [source,java] ---- -package org.acme.security.webauthn; - import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; - -import io.smallrye.common.annotation.Blocking; -import jakarta.enterprise.context.ApplicationScoped; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; import io.quarkus.security.webauthn.WebAuthnUserProvider; +import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; -import io.vertx.ext.auth.webauthn.AttestationCertificates; -import io.vertx.ext.auth.webauthn.Authenticator; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; -import static org.acme.security.webauthn.WebAuthnCredential.findByCredID; -import static org.acme.security.webauthn.WebAuthnCredential.findByUserName; - @Blocking @ApplicationScoped public class MyWebAuthnSetup implements WebAuthnUserProvider { @Transactional @Override - public Uni> findWebAuthnCredentialsByUserName(String userName) { - return Uni.createFrom().item(toAuthenticators(findByUserName(userName))); + public Uni> findByUserName(String userId) { + return Uni.createFrom().item( + WebAuthnCredential.findByUserName(userId) + .stream() + .map(WebAuthnCredential::toWebAuthnCredentialRecord) + .toList()); } @Transactional @Override - public Uni> findWebAuthnCredentialsByCredID(String credID) { - return Uni.createFrom().item(toAuthenticators(findByCredID(credID))); + public Uni findByCredentialId(String credId) { + WebAuthnCredential creds = WebAuthnCredential.findByCredentialId(credId); + if(creds == null) + return Uni.createFrom() + .failure(new RuntimeException("No such credential ID")); + return Uni.createFrom().item(creds.toWebAuthnCredentialRecord()); } @Transactional @Override - public Uni updateOrStoreWebAuthnCredentials(Authenticator authenticator) { - // leave the scooby user to the manual endpoint, because if we do it here it will be created/updated twice - if(!authenticator.getUserName().equals("scooby")) { - User user = User.findByUserName(authenticator.getUserName()); - if(user == null) { - // new user - User newUser = new User(); - newUser.userName = authenticator.getUserName(); - WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser); - credential.persist(); - newUser.persist(); - } else { - // existing user - user.webAuthnCredential.counter = authenticator.getCounter(); - } - } - return Uni.createFrom().nullItem(); + public Uni store(WebAuthnCredentialRecord credentialRecord) { + User newUser = new User(); + // We can only store one credential per userName thanks to the unicity constraint + // which will cause this transaction to fail and throw if the userName already exists + newUser.userName = credentialRecord.getUserName(); + WebAuthnCredential credential = new WebAuthnCredential(credentialRecord, newUser); + credential.persist(); + newUser.persist(); + return Uni.createFrom().voidItem(); } - private static List toAuthenticators(List dbs) { - return dbs.stream().map(MyWebAuthnSetup::toAuthenticator).collect(Collectors.toList()); - } - - private static Authenticator toAuthenticator(WebAuthnCredential credential) { - Authenticator ret = new Authenticator(); - ret.setAaguid(credential.aaguid); - AttestationCertificates attestationCertificates = new AttestationCertificates(); - attestationCertificates.setAlg(credential.alg); - ret.setAttestationCertificates(attestationCertificates); - ret.setCounter(credential.counter); - ret.setCredID(credential.credID); - ret.setFmt(credential.fmt); - ret.setPublicKey(credential.publicKey); - ret.setType(credential.type); - ret.setUserName(credential.userName); - return ret; + @Transactional + @Override + public Uni update(String credentialId, long counter) { + WebAuthnCredential credential = + WebAuthnCredential.findByCredentialId(credentialId); + credential.counter = counter; + return Uni.createFrom().voidItem(); } @Override @@ -496,6 +400,25 @@ public class MyWebAuthnSetup implements WebAuthnUserProvider { } ---- +Warning: When implementing your own `WebAuthnUserProvider.store` method, make sure that you never allow creating +new credentials for a `userName` that already exists. Otherwise you risk allowing third-parties to impersonate existing +users by letting them add their own credentials to existing accounts. If you want to allow existing users to register +more than one WebAuthn credential, you must make sure in `WebAuthnUserProvider.store` that the user is currently logged +in under the same `userName` to which you want to add new credentials. In every other case, make sure to return a failed +`Uni` from this method. In this particular example, this is checked using a unicity constraint on the user name, which +will cause the transaction to fail if the user already exists. + +== Configuration + +Because we want to delegate login and registration to the default Quarkus WebAuthn endpoints, we need to enable them +in configuration (`src/main/resources/application.properties`): + +[source,properties] +---- +quarkus.webauthn.enable-login-endpoint=true +quarkus.webauthn.enable-registration-endpoint=true +---- + == Writing the HTML application We now need to write a web page with links to all our APIs, as well as a way to register a new user, login, and logout, @@ -563,7 +486,6 @@ in `src/main/resources/META-INF/resources/index.html`:

Login

-

@@ -578,11 +500,7 @@ in `src/main/resources/META-INF/resources/index.html`: + +---- + +Or, if you need to customise the endpoints: + [source,javascript] ---- ---- +=== CSRF considerations + +If you use the endpoints provided by Quarkus, they will not be protected by xdoc:security-csrf-prevention.adoc[CSRF], but +if you define your own endpoints and use this JavaScript library to access them you will need to configure CSRF via headers: + +[source,javascript] +---- + + +---- + === Invoke registration -The `webAuthn.register` method invokes the registration challenge endpoint, then calls the authenticator and invokes the callback endpoint +The `webAuthn.register` method invokes the registration challenge endpoint, then calls the authenticator and invokes the registration endpoint for that registration, and returns a https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise object]: [source,javascript] @@ -847,12 +866,12 @@ webAuthn.register({ name: userName, displayName: firstName + " " + lastName }) === Invoke login -The `webAuthn.login` method invokes the login challenge endpoint, then calls the authenticator and invokes the callback endpoint +The `webAuthn.login` method invokes the login challenge endpoint, then calls the authenticator and invokes the login endpoint for that login, and returns a https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise object]: [source,javascript] ---- -webAuthn.login({ name: userName }) +webAuthn.login({ name: userName }) <1> .then(body => { // do something now that the user is logged in }) @@ -861,16 +880,18 @@ webAuthn.login({ name: userName }) }); ---- +<1> The name is optional, in the case of https://www.w3.org/TR/webauthn-3/#discoverable-credential[Discoverable Credentials] (with PassKeys) + === Only invoke the registration challenge and authenticator -The `webAuthn.registerOnly` method invokes the registration challenge endpoint, then calls the authenticator and returns +The `webAuthn.registerClientSteps` method invokes the registration challenge endpoint, then calls the authenticator and returns a https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise object] containing a -JSON object suitable for being sent to the callback endpoint. You can use that JSON object in order to store the credentials +JSON object suitable for being sent to the registration endpoint. You can use that JSON object in order to store the credentials in hidden form `input` elements, for example, and send it as part of a regular HTML form: [source,javascript] ---- -webAuthn.registerOnly({ name: userName, displayName: firstName + " " + lastName }) +webAuthn.registerClientSteps({ name: userName, displayName: firstName + " " + lastName }) .then(body => { // store the registration JSON in form elements document.getElementById('webAuthnId').value = body.id; @@ -886,14 +907,14 @@ webAuthn.registerOnly({ name: userName, displayName: firstName + " " + lastName === Only invoke the login challenge and authenticator -The `webAuthn.loginOnly` method invokes the login challenge endpoint, then calls the authenticator and returns +The `webAuthn.loginClientSteps` method invokes the login challenge endpoint, then calls the authenticator and returns a https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise object] containing a -JSON object suitable for being sent to the callback endpoint. You can use that JSON object in order to store the credentials +JSON object suitable for being sent to the login endpoint. You can use that JSON object in order to store the credentials in hidden form `input` elements, for example, and send it as part of a regular HTML form: [source,javascript] ---- -webAuthn.loginOnly({ name: userName }) +webAuthn.loginClientSteps({ name: userName }) <1> .then(body => { // store the login JSON in form elements document.getElementById('webAuthnId').value = body.id; @@ -909,25 +930,95 @@ webAuthn.loginOnly({ name: userName }) }); ---- +<1> The name is optional, in the case of https://www.w3.org/TR/webauthn-3/#discoverable-credential[Discoverable Credentials] (with PassKeys) + == Handling login and registration endpoints yourself Sometimes, you will want to ask for more data than just a username in order to register a user, -or you want to deal with login and registration with custom validation, and so the WebAuthn callback -endpoint is not enough. +or you want to deal with login and registration with custom validation, and so the default WebAuthn login +and registration endpoints are not enough. -In this case, you can use the `WebAuthn.loginOnly` and `WebAuthn.registerOnly` methods from the JavaScript +In this case, you can use the `WebAuthn.loginClientSteps` and `WebAuthn.registerClientSteps` methods from the JavaScript library, store the authenticator data in hidden form elements, and send them as part of your form payload to the server to your custom login or registration endpoints. -If you are storing them in form input elements, you can then use the `WebAuthnLoginResponse` and -`WebAuthnRegistrationResponse` classes, mark them as `@BeanParam` and then use the `WebAuthnSecurity.login` -and `WebAuthnSecurity.register` methods to replace the `/q/webauthn/callback` endpoint. This even -allows you to create two separate endpoints for handling login and registration at different endpoints. +If you are storing them in form input elements, you can then use the link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnLoginResponse.html[`WebAuthnLoginResponse`] and +link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnRegistrationResponse.html[`WebAuthnRegistrationResponse`] classes, +mark them as `@BeanParam` and then use the +link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html#login(io.quarkus.security.webauthn.WebAuthnLoginResponse,io.vertx.ext.web.RoutingContext)[`WebAuthnSecurity.login`] +and link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html#register(io.quarkus.security.webauthn.WebAuthnRegisterResponse,io.vertx.ext.web.RoutingContext)[`WebAuthnSecurity.register`] +methods to replace the `/q/webauthn/login` and `/q/webauthn/register` endpoints. -In most cases you can keep using the `/q/webauthn/login` and `/q/webauthn/register` challenge-initiating +In most cases you can keep using the `/q/webauthn/login-options-challenge` and `/q/webauthn/register-options-challenge` challenge-initiating endpoints, because this is not where custom logic is required. -For example, here's how you can handle a custom login and register action: +In this case, the registration flow is a little different because you will write your own registration endpoint +which will handle storing of the credentials and setting up the session cookie: + +image::webauthn-custom-register.svg[role="thumb"] + +Similarly, the login flow is a little different because you will write your own login endpoint +which will handle updating the credentials and setting up the session cookie: + +image::webauthn-custom-login.svg[role="thumb"] + +If you handle user and credential creation and logins yourself in your endpoints, you only need +to provide a read-only view of your entities in your link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`], so you can skip +the `store` and `update` methods: + +[source,java] +---- +package org.acme.security.webauthn; + +import java.util.List; + +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; +import io.quarkus.security.webauthn.WebAuthnUserProvider; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; +import model.WebAuthnCredential; + +@Blocking +@ApplicationScoped +public class MyWebAuthnSetup implements WebAuthnUserProvider { + + @Transactional + @Override + public Uni> findByUserName(String userName) { + return Uni.createFrom().item( + WebAuthnCredential.findByUserName(userName) + .stream() + .map(WebAuthnCredential::toWebAuthnCredentialRecord) + .toList()); + } + + @Transactional + @Override + public Uni findByCredentialId(String credentialId) { + WebAuthnCredential creds = WebAuthnCredential.findByCredentialId(credentialId); + if(creds == null) + return Uni.createFrom() + .failure(new RuntimeException("No such credential ID")); + return Uni.createFrom().item(creds.toWebAuthnCredentialRecord()); + } + + @Override + public Set getRoles(String userId) { + if(userId.equals("admin")) { + return Set.of("user", "admin"); + } + return Collections.singleton("user"); + } +} +---- + +NOTE: When setting up your own login and registration endpoints, you don't need to enable the default endpoints, so you can +remove the `quarkus.webauthn.enable-login-endpoint` and `quarkus.webauthn.enable-registration-endpoint` configuration. + +Thankfully, you can use the link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html[`WebAuthnSecurity`] bean to handle the WebAuthn-specific part of +your registration and login endpoints, and focus on your own logic: [source,java] ---- @@ -943,10 +1034,10 @@ import jakarta.ws.rs.core.Response.Status; import org.jboss.resteasy.reactive.RestForm; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; import io.quarkus.security.webauthn.WebAuthnLoginResponse; import io.quarkus.security.webauthn.WebAuthnRegisterResponse; import io.quarkus.security.webauthn.WebAuthnSecurity; -import io.vertx.ext.auth.webauthn.Authenticator; import io.vertx.ext.web.RoutingContext; @Path("") @@ -955,29 +1046,28 @@ public class LoginResource { @Inject WebAuthnSecurity webAuthnSecurity; - // Provide an alternative implementation of the /q/webauthn/callback endpoint, only for login + // Provide an alternative implementation of the /q/webauthn/login endpoint @Path("/login") @POST @Transactional - public Response login(@RestForm String userName, - @BeanParam WebAuthnLoginResponse webAuthnResponse, + public Response login(@BeanParam WebAuthnLoginResponse webAuthnResponse, RoutingContext ctx) { // Input validation - if(userName == null || userName.isEmpty() || !webAuthnResponse.isSet() || !webAuthnResponse.isValid()) { + if(!webAuthnResponse.isSet() || !webAuthnResponse.isValid()) { return Response.status(Status.BAD_REQUEST).build(); } - User user = User.findByUserName(userName); - if(user == null) { - // Invalid user - return Response.status(Status.BAD_REQUEST).build(); - } try { - Authenticator authenticator = this.webAuthnSecurity.login(webAuthnResponse, ctx).await().indefinitely(); + WebAuthnCredentialRecord credentialRecord = this.webAuthnSecurity.login(webAuthnResponse, ctx).await().indefinitely(); + User user = User.findByUserName(credentialRecord.getUserName()); + if(user == null) { + // Invalid user + return Response.status(Status.BAD_REQUEST).build(); + } // bump the auth counter - user.webAuthnCredential.counter = authenticator.getCounter(); + user.webAuthnCredential.counter = credentialRecord.getCounter(); // make a login cookie - this.webAuthnSecurity.rememberUser(authenticator.getUserName(), ctx); + this.webAuthnSecurity.rememberUser(credentialRecord.getUserName(), ctx); return Response.ok().build(); } catch (Exception exception) { // handle login failure - make a proper error response @@ -985,7 +1075,7 @@ public class LoginResource { } } - // Provide an alternative implementation of the /q/webauthn/callback endpoint, only for registration + // Provide an alternative implementation of the /q/webauthn/register endpoint @Path("/register") @POST @Transactional @@ -993,7 +1083,8 @@ public class LoginResource { @BeanParam WebAuthnRegisterResponse webAuthnResponse, RoutingContext ctx) { // Input validation - if(userName == null || userName.isEmpty() || !webAuthnResponse.isSet() || !webAuthnResponse.isValid()) { + if(userName == null || userName.isEmpty() + || !webAuthnResponse.isSet() || !webAuthnResponse.isValid()) { return Response.status(Status.BAD_REQUEST).build(); } @@ -1004,10 +1095,12 @@ public class LoginResource { } try { // store the user - Authenticator authenticator = this.webAuthnSecurity.register(webAuthnResponse, ctx).await().indefinitely(); + WebAuthnCredentialRecord credentialRecord = + webAuthnSecurity.register(userName, webAuthnResponse, ctx).await().indefinitely(); User newUser = new User(); - newUser.userName = authenticator.getUserName(); - WebAuthnCredential credential = new WebAuthnCredential(authenticator, newUser); + newUser.userName = credentialRecord.getUserName(); + WebAuthnCredential credential = + new WebAuthnCredential(credentialRecord, newUser); credential.persist(); newUser.persist(); // make a login cookie @@ -1022,28 +1115,32 @@ public class LoginResource { } ---- -NOTE: The `WebAuthnSecurity` methods do not set or read the user cookie, so you will have to take care +NOTE: The link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html[`WebAuthnSecurity`] +methods do not set or read the <>, so you will have to take care of it yourself, but it allows you to use other means of storing the user, such as JWT. You can use the -`rememberUser(String userName, RoutingContext ctx)` and `logout(RoutingContext ctx)` methods on the same -`WebAuthnSecurity` class if you want to manually set up login cookies. +link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html#rememberUser(java.lang.String,io.vertx.ext.web.RoutingContext)[`WebAuthnSecurity.rememberUser`] + and link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html#logout(io.vertx.ext.web.RoutingContext)[`WebAuthnSecurity.logout`] + methods on the same link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html[`WebAuthnSecurity`] class if you want to manually set up login cookies. == Blocking version -If you're using a blocking data access to the database, you can safely block on the `WebAuthnSecurity` methods, +If you're using a blocking data access to the database, you can safely block on the +link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html[`WebAuthnSecurity`] methods, with `.await().indefinitely()`, because nothing is async in the `register` and `login` methods, besides the -data access with your `WebAuthnUserProvider`. +data access with your link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`]. -You will have to add the `@Blocking` annotation on your `WebAuthnUserProvider` class in order to tell the +You will have to add the `@Blocking` annotation on your link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`] class in order for the Quarkus WebAuthn endpoints to defer those calls to the worker pool. == Virtual-Threads version -If you're using a blocking data access to the database, you can safely block on the `WebAuthnSecurity` methods, +If you're using a blocking data access to the database, you can safely block on the +link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnSecurity.html[`WebAuthnSecurity`] methods, with `.await().indefinitely()`, because nothing is async in the `register` and `login` methods, besides the -data access with your `WebAuthnUserProvider`. +data access with your link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`]. -You will have to add the `@RunOnVirtualThread` annotation on your `WebAuthnUserProvider` class in order to tell the -Quarkus WebAuthn endpoints to defer those calls to virtual threads. +You will have to add the `@RunOnVirtualThread` annotation on your link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`] class in order to tell the +Quarkus WebAuthn endpoints to defer those calls to the worker pool. == Testing WebAuthn @@ -1066,8 +1163,10 @@ Testing WebAuthn can be complicated because normally you need a hardware token, testImplementation("io.quarkus:quarkus-test-security-webauthn") ---- -With this, you can use `WebAuthnHardware` to emulate an authenticator token, as well as the -`WebAuthnEndpointHelper` helper methods in order to invoke the WebAuthn endpoints, or even fill your form +With this, you can use link:{webauthn-test-api}/io/quarkus/test/security/webauthn/WebAuthnHardware.html[`WebAuthnHardware`] +to emulate an authenticator token, as well as the +link:{webauthn-test-api}/io/quarkus/test/security/webauthn/WebAuthnEndpointHelper.html[`WebAuthnEndpointHelper`] +helper methods in order to invoke the WebAuthn endpoints, or even fill your form data for custom endpoints: [source,java] @@ -1076,25 +1175,24 @@ package org.acme.security.webauthn.test; import static io.restassured.RestAssured.given; +import java.net.URL; import java.util.function.Consumer; -import java.util.function.Supplier; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -import io.quarkus.security.webauthn.WebAuthnController; +import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.webauthn.WebAuthnEndpointHelper; import io.quarkus.test.security.webauthn.WebAuthnHardware; import io.restassured.RestAssured; import io.restassured.filter.Filter; -import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import io.vertx.core.json.JsonObject; @QuarkusTest public class WebAuthnResourceTest { - + enum User { USER, ADMIN; } @@ -1102,6 +1200,9 @@ public class WebAuthnResourceTest { DEFAULT, MANUAL; } + @TestHTTPResource + URL url; + @Test public void testWebAuthnUser() { testWebAuthn("FroMage", User.USER, Endpoint.DEFAULT); @@ -1112,42 +1213,41 @@ public class WebAuthnResourceTest { public void testWebAuthnAdmin() { testWebAuthn("admin", User.ADMIN, Endpoint.DEFAULT); } - + private void testWebAuthn(String userName, User user, Endpoint endpoint) { Filter cookieFilter = new RenardeCookieFilter(); - WebAuthnHardware token = new WebAuthnHardware(); + WebAuthnHardware token = new WebAuthnHardware(url); verifyLoggedOut(cookieFilter); // two-step registration - String challenge = WebAuthnEndpointHelper.invokeRegistration(userName, cookieFilter); + String challenge = WebAuthnEndpointHelper.obtainRegistrationChallenge(userName, cookieFilter); JsonObject registrationJson = token.makeRegistrationJson(challenge); if(endpoint == Endpoint.DEFAULT) - WebAuthnEndpointHelper.invokeCallback(registrationJson, cookieFilter); + WebAuthnEndpointHelper.invokeRegistration(userName, registrationJson, cookieFilter); else { invokeCustomEndpoint("/register", cookieFilter, request -> { WebAuthnEndpointHelper.addWebAuthnRegistrationFormParameters(request, registrationJson); request.formParam("userName", userName); }); } - + // verify that we can access logged-in endpoints verifyLoggedIn(cookieFilter, userName, user); - + // logout WebAuthnEndpointHelper.invokeLogout(cookieFilter); - + verifyLoggedOut(cookieFilter); - + // two-step login - challenge = WebAuthnEndpointHelper.invokeLogin(userName, cookieFilter); + challenge = WebAuthnEndpointHelper.obtainLoginChallenge(null, cookieFilter); JsonObject loginJson = token.makeLoginJson(challenge); if(endpoint == Endpoint.DEFAULT) - WebAuthnEndpointHelper.invokeCallback(loginJson, cookieFilter); + WebAuthnEndpointHelper.invokeLogin(loginJson, cookieFilter); else { invokeCustomEndpoint("/login", cookieFilter, request -> { WebAuthnEndpointHelper.addWebAuthnLoginFormParameters(request, loginJson); - request.formParam("userName", userName); }); } @@ -1156,7 +1256,7 @@ public class WebAuthnResourceTest { // logout WebAuthnEndpointHelper.invokeLogout(cookieFilter); - + verifyLoggedOut(cookieFilter); } @@ -1173,7 +1273,6 @@ public class WebAuthnResourceTest { .statusCode(200) .log().ifValidationFails() .cookie(WebAuthnEndpointHelper.getChallengeCookie(), Matchers.is("")) - .cookie(WebAuthnEndpointHelper.getChallengeUsernameCookie(), Matchers.is("")) .cookie(WebAuthnEndpointHelper.getMainCookie(), Matchers.notNullValue()); } @@ -1200,7 +1299,7 @@ public class WebAuthnResourceTest { .then() .statusCode(200) .body(Matchers.is(userName)); - + // admin API? if(user == User.ADMIN) { RestAssured.given().filter(cookieFilter) @@ -1243,7 +1342,7 @@ public class WebAuthnResourceTest { .then() .statusCode(302) .header("Location", Matchers.is("http://localhost:8081/")); - + // admin API not accessible RestAssured.given() .filter(cookieFilter) @@ -1258,32 +1357,45 @@ public class WebAuthnResourceTest { ---- For this test, since we're testing both the provided callback endpoint, which updates users -in its `WebAuthnUserProvider` and the manual `LoginResource` endpoint, which deals with users -manually, we need to override the `WebAuthnUserProvider` with one that doesn't update the +in its link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`] and the manual `LoginResource` endpoint, which deals with users +manually, we need to override the link:{webauthn-api}/io/quarkus/security/webauthn/WebAuthnUserProvider.html[`WebAuthnUserProvider`] with one that doesn't update the `scooby` user: [source,java] ---- package org.acme.security.webauthn.test; -import jakarta.enterprise.context.ApplicationScoped; - import org.acme.security.webauthn.MyWebAuthnSetup; +import org.acme.security.webauthn.WebAuthnCredential; +import io.quarkus.security.webauthn.WebAuthnCredentialRecord; import io.quarkus.test.Mock; import io.smallrye.mutiny.Uni; -import io.vertx.ext.auth.webauthn.Authenticator; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; @Mock @ApplicationScoped public class TestUserProvider extends MyWebAuthnSetup { + @Transactional + @Override + public Uni store(WebAuthnCredentialRecord credentialRecord) { + // this user is handled in the LoginResource endpoint manually + if (credentialRecord.getUserName().equals("scooby")) { + return Uni.createFrom().voidItem(); + } + return super.store(credentialRecord); + } + + @Transactional @Override - public Uni updateOrStoreWebAuthnCredentials(Authenticator authenticator) { - // delegate the scooby user to the manual endpoint, because if we do it here it will be - // created/updated twice - if(authenticator.getUserName().equals("scooby")) - return Uni.createFrom().nullItem(); - return super.updateOrStoreWebAuthnCredentials(authenticator); + public Uni update(String credentialId, long counter) { + WebAuthnCredential credential = WebAuthnCredential.findByCredentialId(credentialId); + // this user is handled in the LoginResource endpoint manually + if (credential.user.userName.equals("scooby")) { + return Uni.createFrom().voidItem(); + } + return super.update(credentialId, counter); } } ----