2
0

passkeys.adoc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. [[passkeys]]
  2. = Passkeys
  3. Spring Security provides support for https://www.passkeys.com[passkeys].
  4. Passkeys are a more secure method of authenticating than passwords and are built using https://www.w3.org/TR/webauthn-3/[WebAuthn].
  5. In order to use a passkey to authenticate, a user must first xref:servlet/authentication/passkeys.adoc#passkeys-register[Register a New Credential].
  6. After the credential is registered, it can be used to authenticate by xref:servlet/authentication/passkeys.adoc#passkeys-verify[verifying an authentication assertion].
  7. [[passkeys-configuration]]
  8. == Configuration
  9. The following configuration enables passkey authentication.
  10. 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].
  11. [tabs]
  12. ======
  13. Java::
  14. +
  15. [source,java,role="primary"]
  16. ----
  17. @Bean
  18. SecurityFilterChain filterChain(HttpSecurity http) {
  19. http
  20. // ...
  21. .formLogin(withDefaults())
  22. .webAuthn((webAuthn) -> webAuthn
  23. .rpName("Spring Security Relying Party")
  24. .rpId("example.com")
  25. .allowedOrigins("https://example.com")
  26. );
  27. return http.build();
  28. }
  29. @Bean
  30. UserDetailsService userDetailsService() {
  31. UserDetails userDetails = User.withDefaultPasswordEncoder()
  32. .username("user")
  33. .password("password")
  34. .roles("USER")
  35. .build();
  36. return new InMemoryUserDetailsManager(userDetails);
  37. }
  38. ----
  39. Kotlin::
  40. +
  41. [source,kotlin,role="secondary"]
  42. ----
  43. @Bean
  44. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  45. http {
  46. webAuthn {
  47. rpName = "Spring Security Relying Party"
  48. rpId = "example.com"
  49. allowedOrigins = setOf("https://example.com")
  50. }
  51. }
  52. }
  53. @Bean
  54. open fun userDetailsService(): UserDetailsService {
  55. val userDetails = User.withDefaultPasswordEncoder()
  56. .username("user")
  57. .password("password")
  58. .roles("USER")
  59. .build()
  60. return InMemoryUserDetailsManager(userDetails)
  61. }
  62. ----
  63. ======
  64. [[passkeys-register]]
  65. == Register a New Credential
  66. 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].
  67. Registering a new credential is composed of two steps:
  68. 1. Requesting the Registration Options
  69. 2. Registering the Credential
  70. [[passkeys-register-options]]
  71. === Request the Registration Options
  72. The first step in registration of a new credential is to request the registration options.
  73. In Spring Security, a request for the registration options is typically done using JavaScript and looks like:
  74. [NOTE]
  75. ====
  76. Spring Security provides a default registration page that can be used as a reference on how to register credentials.
  77. ====
  78. .Request for Registration Options
  79. [source,http]
  80. ----
  81. POST /webauthn/register/options
  82. X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
  83. ----
  84. The request above will obtain the registration options for the currently authenticated user.
  85. 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.
  86. .Response for Registration Options
  87. [source,json]
  88. ----
  89. {
  90. "rp": {
  91. "name": "SimpleWebAuthn Example",
  92. "id": "example.localhost"
  93. },
  94. "user": {
  95. "name": "user@example.localhost",
  96. "id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
  97. "displayName": "user@example.localhost"
  98. },
  99. "challenge": "q7lCdd3SVQxdC-v8pnRAGEn1B2M-t7ZECWPwCAmhWvc",
  100. "pubKeyCredParams": [
  101. {
  102. "type": "public-key",
  103. "alg": -8
  104. },
  105. {
  106. "type": "public-key",
  107. "alg": -7
  108. },
  109. {
  110. "type": "public-key",
  111. "alg": -257
  112. }
  113. ],
  114. "timeout": 300000,
  115. "excludeCredentials": [],
  116. "authenticatorSelection": {
  117. "residentKey": "required",
  118. "userVerification": "preferred"
  119. },
  120. "attestation": "direct",
  121. "extensions": {
  122. "credProps": true
  123. }
  124. }
  125. ----
  126. [[passkeys-register-create]]
  127. === Registering the Credential
  128. After the registration options are obtained, they are used to create the credentials that are registered.
  129. 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`.
  130. The returned value can then be sent to the server as a JSON request.
  131. An example registration request can be found below:
  132. .Example Registration Request
  133. [source,http]
  134. ----
  135. POST /webauthn/register
  136. X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
  137. {
  138. "publicKey": { // <1>
  139. "credential": {
  140. "id": "dYF7EGnRFFIXkpXi9XU2wg",
  141. "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
  142. "response": {
  143. "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEHWBexBp0RRSF5KV4vV1NsKlAQIDJiABIVggQjmrekPGzyqtoKK9HPUH-8Z2FLpoqkklFpFPQVICQ3IiWCD6I9Jvmor685fOZOyGXqUd87tXfvJk8rxj9OhuZvUALA",
  144. "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSl9RTi10SFJYRWVKYjlNcUNrWmFPLUdOVmlibXpGVGVWMk43Z0ptQUdrQSIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
  145. "transports": [
  146. "internal",
  147. "hybrid"
  148. ]
  149. },
  150. "type": "public-key",
  151. "clientExtensionResults": {},
  152. "authenticatorAttachment": "platform"
  153. },
  154. "label": "1password" // <2>
  155. }
  156. }
  157. ----
  158. <1> The result of calling `navigator.credentials.create` with binary values base64url encoded.
  159. <2> A label that the user selects to have associated with this credential to help the user distinguish the credential.
  160. .Example Successful Registration Response
  161. [source,http]
  162. ----
  163. HTTP/1.1 200 OK
  164. {
  165. "success": true
  166. }
  167. ----
  168. [[passkeys-verify]]
  169. == Verifying an Authentication Assertion
  170. After xref:./passkeys.adoc#passkeys-register[] the passkey can be https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion[verified] (authenticated).
  171. Verifying a credential is composed of two steps:
  172. 1. Requesting the Verification Options
  173. 2. Verifying the Credential
  174. [[passkeys-verify-options]]
  175. === Request the Verification Options
  176. The first step in verification of a credential is to request the verification options.
  177. In Spring Security, a request for the verification options is typically done using JavaScript and looks like:
  178. [NOTE]
  179. ====
  180. Spring Security provides a default log in page that can be used as a reference on how to verify credentials.
  181. ====
  182. .Request for Verification Options
  183. [source,http]
  184. ----
  185. POST /webauthn/authenticate/options
  186. X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
  187. ----
  188. The request above will obtain the verification options.
  189. 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.
  190. The response will contain the options for obtaining a credential with binary values such as `challenge` base64url encoded.
  191. .Example Response for Verification Options
  192. [source,json]
  193. ----
  194. {
  195. "challenge": "cQfdGrj9zDg3zNBkOH3WPL954FTOShVy0-CoNgSewNM",
  196. "timeout": 300000,
  197. "rpId": "example.localhost",
  198. "allowCredentials": [],
  199. "userVerification": "preferred",
  200. "extensions": {}
  201. }
  202. ----
  203. [[passkeys-verify-get]]
  204. === Verifying the Credential
  205. After the verification options are obtained, they are used to get a credential.
  206. 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`.
  207. The returned value of `navigator.credentials.get` can then be sent to the server as a JSON request.
  208. Binary values such as `rawId` and `response.*` must be base64url encoded.
  209. An example authentication request can be found below:
  210. .Example Authentication Request
  211. [source,http]
  212. ----
  213. POST /login/webauthn
  214. X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
  215. {
  216. "id": "dYF7EGnRFFIXkpXi9XU2wg",
  217. "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
  218. "response": {
  219. "authenticatorData": "y9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNgdAAAAAA",
  220. "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRFVsRzRDbU9naWhKMG1vdXZFcE9HdUk0ZVJ6MGRRWmxUQmFtbjdHQ1FTNCIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
  221. "signature": "MEYCIQCW2BcUkRCAXDmGxwMi78jknenZ7_amWrUJEYoTkweldAIhAMD0EMp1rw2GfwhdrsFIeDsL7tfOXVPwOtfqJntjAo4z",
  222. "userHandle": "Q3_0Xd64_HW0BlKRAJnVagJTpLKLgARCj8zjugpRnVo"
  223. },
  224. "clientExtensionResults": {},
  225. "authenticatorAttachment": "platform"
  226. }
  227. ----
  228. .Example Successful Authentication Response
  229. [source,http]
  230. ----
  231. HTTP/1.1 200 OK
  232. {
  233. "redirectUrl": "/", // <1>
  234. "authenticated": true // <2>
  235. }
  236. ----
  237. <1> The URL to redirect to
  238. <2> Indicates that the user is authenticated
  239. .Example Authentication Failure Response
  240. [source,http]
  241. ----
  242. HTTP/1.1 401 OK
  243. ----