| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 | 
							- [[passkeys]]
 
- = Passkeys
 
- Spring Security provides support for https://www.passkeys.com[passkeys].
 
- Passkeys are a more secure method of authenticating than passwords and are built using https://www.w3.org/TR/webauthn-3/[WebAuthn].
 
- In order to use a passkey to authenticate, a user must first xref:servlet/authentication/passkeys.adoc#passkeys-register[Register a New Credential].
 
- After the credential is registered, it can be used to authenticate by xref:servlet/authentication/passkeys.adoc#passkeys-verify[verifying an authentication assertion].
 
- [[passkeys-dependencies]]
 
- == Required Dependencies
 
- To get started, add the `webauthn4j-core` dependency to your project.
 
- [NOTE]
 
- ====
 
- This assumes that you are managing Spring Security's versions with Spring Boot or Spring Security's BOM as described in xref:getting-spring-security.adoc[].
 
- ====
 
- .Passkeys Dependencies
 
- [tabs]
 
- ======
 
- Maven::
 
- +
 
- [source,xml,role="primary",subs="verbatim,attributes"]
 
- ----
 
- <dependency>
 
-     <groupId>org.springframework.security</groupId>
 
-     <artifactId>spring-security-web</artifactId>
 
- </dependency>
 
- <dependency>
 
-     <groupId>com.webauthn4j</groupId>
 
-     <artifactId>webauthn4j-core</artifactId>
 
-     <version>{webauthn4j-core-version}</version>
 
- </dependency>
 
- ----
 
- Gradle::
 
- +
 
- [source,groovy,role="secondary",subs="verbatim,attributes"]
 
- ----
 
- depenendencies {
 
-     implementation "org.springframework.security:spring-security-web"
 
-     implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}"
 
- }
 
- ----
 
- ======
 
- [[passkeys-configuration]]
 
- == Configuration
 
- The following configuration enables passkey authentication.
 
- It provides a way to xref:./passkeys.adoc#passkeys-register[] at `/webauthn/register` and a default log in page that allows xref:./passkeys.adoc#passkeys-verify[authenticating with passkeys].
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- SecurityFilterChain filterChain(HttpSecurity http) {
 
- 	// ...
 
- 	http
 
- 		// ...
 
- 		.formLogin(withDefaults())
 
- 		.webAuthn((webAuthn) -> webAuthn
 
- 			.rpName("Spring Security Relying Party")
 
- 			.rpId("example.com")
 
- 			.allowedOrigins("https://example.com")
 
- 			// optional properties
 
- 			.creationOptionsRepository(new CustomPublicKeyCredentialCreationOptionsRepository())
 
- 			.messageConverter(new CustomHttpMessageConverter())
 
- 		);
 
- 	return http.build();
 
- }
 
- @Bean
 
- UserDetailsService userDetailsService() {
 
- 	UserDetails userDetails = User.withDefaultPasswordEncoder()
 
- 		.username("user")
 
- 		.password("password")
 
- 		.roles("USER")
 
- 		.build();
 
- 	return new InMemoryUserDetailsManager(userDetails);
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 
- 	// ...
 
- 	http {
 
- 		webAuthn {
 
- 			rpName = "Spring Security Relying Party"
 
- 			rpId = "example.com"
 
- 			allowedOrigins = setOf("https://example.com")
 
- 			// optional properties
 
- 			creationOptionsRepository = CustomPublicKeyCredentialCreationOptionsRepository()
 
- 			messageConverter = CustomHttpMessageConverter()
 
- 		}
 
- 	}
 
- }
 
- @Bean
 
- open fun userDetailsService(): UserDetailsService {
 
- 	val userDetails = User.withDefaultPasswordEncoder()
 
- 		.username("user")
 
- 		.password("password")
 
- 		.roles("USER")
 
- 		.build()
 
- 	return InMemoryUserDetailsManager(userDetails)
 
- }
 
- ----
 
- ======
 
- [[passkeys-configuration-persistence]]
 
- === JDBC & Custom Persistence
 
- WebAuthn performs persistence with javadoc:org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository[] and javadoc:org.springframework.security.web.webauthn.management.UserCredentialRepository[].
 
- The default is to use in memory persistence, but JDBC persistence is support with javadoc:org.springframework.security.web.webauthn.management.JdbcPublicKeyCredentialUserEntityRepository[] and javadoc:org.springframework.security.web.webauthn.management.JdbcUserCredentialRepository[].
 
- To configure JDBC based persistence, expose the repositories as a Bean:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- JdbcPublicKeyCredentialUserEntityRepository jdbcPublicKeyCredentialRepository(JdbcOperations jdbc) {
 
- 	return new JdbcPublicKeyCredentialUserEntityRepository(jdbc);
 
- }
 
- @Bean
 
- JdbcUserCredentialRepository jdbcUserCredentialRepository(JdbcOperations jdbc) {
 
- 	return new JdbcUserCredentialRepository(jdbc);
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- fun jdbcPublicKeyCredentialRepository(jdbc: JdbcOperations): JdbcPublicKeyCredentialUserEntityRepository {
 
-     return JdbcPublicKeyCredentialUserEntityRepository(jdbc)
 
- }
 
- @Bean
 
- fun jdbcUserCredentialRepository(jdbc: JdbcOperations): JdbcUserCredentialRepository {
 
-     return JdbcUserCredentialRepository(jdbc)
 
- }
 
- ----
 
- ======
 
- If JDBC does not meet your needs, you can create your own implementations of the interfaces and use them by exposing them as a Bean similar to the example above.
 
- [[passkeys-configuration-pkccor]]
 
- === Custom PublicKeyCredentialCreationOptionsRepository
 
- The `PublicKeyCredentialCreationOptionsRepository` is used to persist the `PublicKeyCredentialCreationOptions` between requests.
 
- The default is to persist it the `HttpSession`, but at times users may need to customize this behavior.
 
- This can be done by setting the optional property `creationOptionsRepository` demonstrated in xref:./passkeys.adoc#passkeys-configuration[Configuration] or by exposing a `PublicKeyCredentialCreationOptionsRepository` Bean:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- CustomPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
 
- 	return new CustomPublicKeyCredentialCreationOptionsRepository();
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Bean
 
- open fun creationOptionsRepository(): CustomPublicKeyCredentialCreationOptionsRepository {
 
- 	return CustomPublicKeyCredentialCreationOptionsRepository()
 
- }
 
- ----
 
- ======
 
- [[passkeys-register]]
 
- == Register a New Credential
 
- In order to use a passkey, a user must first https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential[Register a New Credential].
 
- Registering a new credential is composed of two steps:
 
- 1. Requesting the Registration Options
 
- 2. Registering the Credential
 
- [[passkeys-register-options]]
 
- === Request the Registration Options
 
- The first step in registration of a new credential is to request the registration options.
 
- In Spring Security, a request for the registration options is typically done using JavaScript and looks like:
 
- [NOTE]
 
- ====
 
- Spring Security provides a default registration page that can be used as a reference on how to register credentials.
 
- ====
 
- .Request for Registration Options
 
- [source,http]
 
- ----
 
- POST /webauthn/register/options
 
- X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
 
- ----
 
- The request above will obtain the registration options for the currently authenticated user.
 
- Since the challenge is persisted (state is changed) to be compared at the time of registration, the request must be a POST and include a CSRF token.
 
- .Response for Registration Options
 
- [source,json]
 
- ----
 
- {
 
-   "rp": {
 
-     "name": "SimpleWebAuthn Example",
 
-     "id": "example.localhost"
 
-   },
 
-   "user": {
 
-     "name": "user@example.localhost",
 
-     "id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
 
-     "displayName": "user@example.localhost"
 
-   },
 
-   "challenge": "q7lCdd3SVQxdC-v8pnRAGEn1B2M-t7ZECWPwCAmhWvc",
 
-   "pubKeyCredParams": [
 
-     {
 
-       "type": "public-key",
 
-       "alg": -8
 
-     },
 
-     {
 
-       "type": "public-key",
 
-       "alg": -7
 
-     },
 
-     {
 
-       "type": "public-key",
 
-       "alg": -257
 
-     }
 
-   ],
 
-   "timeout": 300000,
 
-   "excludeCredentials": [],
 
-   "authenticatorSelection": {
 
-     "residentKey": "required",
 
-     "userVerification": "preferred"
 
-   },
 
-   "attestation": "none",
 
-   "extensions": {
 
-     "credProps": true
 
-   }
 
- }
 
- ----
 
- [[passkeys-register-create]]
 
- === Registering the Credential
 
- After the registration options are obtained, they are used to create the credentials that are registered.
 
- To register a new credential, the application should pass the options to https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create[`navigator.credentials.create`] after base64url decoding the binary values such as `user.id`, `challenge`, and `excludeCredentials[].id`.
 
- The returned value can then be sent to the server as a JSON request.
 
- An example registration request can be found below:
 
- .Example Registration Request
 
- [source,http]
 
- ----
 
- POST /webauthn/register
 
- X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
 
- {
 
-   "publicKey": { // <1>
 
-     "credential": {
 
-       "id": "dYF7EGnRFFIXkpXi9XU2wg",
 
-       "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
 
-       "response": {
 
-         "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEHWBexBp0RRSF5KV4vV1NsKlAQIDJiABIVggQjmrekPGzyqtoKK9HPUH-8Z2FLpoqkklFpFPQVICQ3IiWCD6I9Jvmor685fOZOyGXqUd87tXfvJk8rxj9OhuZvUALA",
 
-         "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSl9RTi10SFJYRWVKYjlNcUNrWmFPLUdOVmlibXpGVGVWMk43Z0ptQUdrQSIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
 
-         "transports": [
 
-           "internal",
 
-           "hybrid"
 
-         ]
 
-       },
 
-       "type": "public-key",
 
-       "clientExtensionResults": {},
 
-       "authenticatorAttachment": "platform"
 
-     },
 
-     "label": "1password" // <2>
 
-   }
 
- }
 
- ----
 
- <1> The result of calling `navigator.credentials.create` with binary values base64url encoded.
 
- <2> A label that the user selects to have associated with this credential to help the user distinguish the credential.
 
- .Example Successful Registration Response
 
- [source,http]
 
- ----
 
- HTTP/1.1 200 OK
 
- {
 
-   "success": true
 
- }
 
- ----
 
- [[passkeys-verify]]
 
- == Verifying an Authentication Assertion
 
- After xref:./passkeys.adoc#passkeys-register[] the passkey can be https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion[verified] (authenticated).
 
- Verifying a credential is composed of two steps:
 
- 1. Requesting the Verification Options
 
- 2. Verifying the Credential
 
- [[passkeys-verify-options]]
 
- === Request the Verification Options
 
- The first step in verification of a credential is to request the verification options.
 
- In Spring Security, a request for the verification options is typically done using JavaScript and looks like:
 
- [NOTE]
 
- ====
 
- Spring Security provides a default log in page that can be used as a reference on how to verify credentials.
 
- ====
 
- .Request for Verification Options
 
- [source,http]
 
- ----
 
- POST /webauthn/authenticate/options
 
- X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
 
- ----
 
- The request above will obtain the verification options.
 
- Since the challenge is persisted (state is changed) to be compared at the time of authentication, the request must be a POST and include a CSRF token.
 
- The response will contain the options for obtaining a credential with binary values such as `challenge` base64url encoded.
 
- .Example Response for Verification Options
 
- [source,json]
 
- ----
 
- {
 
-   "challenge": "cQfdGrj9zDg3zNBkOH3WPL954FTOShVy0-CoNgSewNM",
 
-   "timeout": 300000,
 
-   "rpId": "example.localhost",
 
-   "allowCredentials": [],
 
-   "userVerification": "preferred",
 
-   "extensions": {}
 
- }
 
- ----
 
- [[passkeys-verify-get]]
 
- === Verifying the Credential
 
- After the verification options are obtained, they are used to get a credential.
 
- To get a credential, the application should pass the options to https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create[`navigator.credentials.get`] after base64url decoding the binary values such as `challenge`.
 
- The returned value of `navigator.credentials.get` can then be sent to the server as a JSON request.
 
- Binary values such as `rawId` and `response.*` must be base64url encoded.
 
- An example authentication request can be found below:
 
- .Example Authentication Request
 
- [source,http]
 
- ----
 
- POST /login/webauthn
 
- X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
 
- {
 
-   "id": "dYF7EGnRFFIXkpXi9XU2wg",
 
-   "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
 
-   "response": {
 
-     "authenticatorData": "y9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNgdAAAAAA",
 
-     "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRFVsRzRDbU9naWhKMG1vdXZFcE9HdUk0ZVJ6MGRRWmxUQmFtbjdHQ1FTNCIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
 
-     "signature": "MEYCIQCW2BcUkRCAXDmGxwMi78jknenZ7_amWrUJEYoTkweldAIhAMD0EMp1rw2GfwhdrsFIeDsL7tfOXVPwOtfqJntjAo4z",
 
-     "userHandle": "Q3_0Xd64_HW0BlKRAJnVagJTpLKLgARCj8zjugpRnVo"
 
-   },
 
-   "clientExtensionResults": {},
 
-   "authenticatorAttachment": "platform"
 
- }
 
- ----
 
- .Example Successful Authentication Response
 
- [source,http]
 
- ----
 
- HTTP/1.1 200 OK
 
- {
 
-   "redirectUrl": "/", // <1>
 
-   "authenticated": true // <2>
 
- }
 
- ----
 
- <1> The URL to redirect to
 
- <2> Indicates that the user is authenticated
 
- .Example Authentication Failure Response
 
- [source,http]
 
- ----
 
- HTTP/1.1 401 OK
 
- ----
 
 
  |