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
- ----
|