1
- == java-webauthn-server
2
-
3
- _Note: This is a work in progress. The https://www.w3.org/TR/webauthn/[Web
4
- Authentication standard] is not yet finished, and additional pre-1.0 releases of
5
- this library may introduce breaking API changes._
1
+ java-webauthn-server
2
+ ====================
3
+ :toc:
4
+ :toc-placement: macro
5
+ :toc-title:
6
6
7
7
image:https://travis-ci.org/Yubico/java-webauthn-server.svg?branch=master["Build Status", link="https://travis-ci.org/Yubico/java-webauthn-server"]
8
8
image:https://coveralls.io/repos/github/Yubico/java-webauthn-server/badge.svg["Coverage Status", link="https://coveralls.io/github/Yubico/java-webauthn-server"]
@@ -14,18 +14,293 @@ for a server to support Web Authentication. This includes registering
14
14
authenticators and authenticating registered authenticators.
15
15
16
16
17
- === Planned breaking changes
17
+ == Table of contents
18
+
19
+ toc::[]
20
+
21
+
22
+ == Dependency configuration
23
+
24
+ Maven:
25
+
26
+ ----------
27
+ <dependency>
28
+ <groupId>com.yubico</groupId>
29
+ <artifactId>webauthn-server-core</artifactId>
30
+ <version>1.0.0-RC2</version>
31
+ <scope>compile</scope>
32
+ </dependency>
33
+ ----------
34
+
35
+ Gradle:
36
+
37
+ ----------
38
+ compile 'com.yubico:webauthn-server-core:1.0.0-RC2'
39
+ ----------
40
+
41
+
42
+ == Features
43
+
44
+ - Generates request objects suitable as parameters to
45
+ `navigator.credentials.create()` and `.get()`
46
+ - Performs all necessary
47
+ https://www.w3.org/TR/webauthn/#rp-operations[validation logic] on the
48
+ response from the client
49
+ - Optionally integrates with a "metadata service" to verify
50
+ https://www.w3.org/TR/webauthn/#sctn-attestation[authenticator attestations]
51
+ and annotate responses with additional authenticator metadata
52
+
53
+
54
+ === Non-features
55
+
56
+ This library has no concept of accounts, sessions, permissions or identity
57
+ federation, and it's not an authentication framework; it only deals with
58
+ executing the WebAuthn authentication mechanism. Sessions, account management
59
+ and other higher level concepts can make use of this authentication mechanism,
60
+ but the authentication mechanism alone does not make a security system.
61
+
62
+
63
+ == Documentation
64
+
65
+ See the
66
+ link:https://yubico.github.io/java-webauthn-server/webauthn-server-core/com/yubico/webauthn/package-summary.html[Javadoc]
67
+ for in-depth API documentation.
68
+
69
+
70
+ == Quick start
71
+
72
+ Implement the
73
+ link:https://yubico.github.io/java-webauthn-server/webauthn-server-core/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
74
+ interface with your database access logic. See
75
+ link:https://github.com/Yubico/java-webauthn-server/blob/master/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java[`InMemoryRegistrationStorage`]
76
+ for an example.
77
+
78
+ Instantiate the
79
+ link:https://yubico.github.io/java-webauthn-server/webauthn-server-core/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
80
+ class:
81
+
82
+ [source,java]
83
+ ----------
84
+ RelyingPartyIdentity rpIdentity = RelyingPartyIdentity.builder()
85
+ .id("example.com")
86
+ .name("Example Application")
87
+ .build();
88
+
89
+ RelyingParty rp = RelyingParty.builder()
90
+ .identity(rpIdentity)
91
+ .credentialRepository(new MyCredentialRepository())
92
+ .build();
93
+ ----------
94
+
95
+
96
+ === Registration
97
+
98
+ Initiate a registration ceremony:
99
+
100
+ [source,java]
101
+ ----------
102
+ byte[] userHandle = new byte[64];
103
+ random.nextBytes(userHandle);
104
+
105
+ PublicKeyCredentialCreationOptions request = rp.startRegistration(StartRegistrationOptions.builder()
106
+ .user(UserIdentity.builder()
107
+ .name("alice")
108
+ .displayName("Alice Hypothetical")
109
+ .id(new ByteArray(userHandle))
110
+ .build())
111
+ .build());
112
+ ----------
113
+
114
+ Serialize `request` to JSON and send it to the client:
115
+
116
+ [source,java]
117
+ ----------
118
+ import com.fasterxml.jackson.databind.ObjectMapper;
119
+
120
+ ObjectMapper jsonMapper = new ObjectMapper()
121
+ .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
122
+ .setSerializationInclusion(Include.NON_ABSENT)
123
+ .registerModule(new Jdk8Module());
124
+
125
+ String json = jsonMapper.writeValueAsString(request);
126
+ return json;
127
+ ----------
128
+
129
+ Get the response from the client:
130
+
131
+ [source,java]
132
+ ----------
133
+ String responseJson = /* ... */;
134
+ PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> pkc =
135
+ jsonMapper.readValue(responseJson, new TypeReference<PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>>(){});
136
+ ----------
137
+
138
+ Validate the response:
139
+
140
+ [source,java]
141
+ ----------
142
+ try {
143
+ RegistrationResult result = rp.finishRegistration(FinishRegistrationOptions.builder()
144
+ .request(request)
145
+ .response(pkc)
146
+ .build());
147
+ } catch (RegistrationFailedException e) { /* ... */ }
148
+ ----------
149
+
150
+ Update your database:
151
+
152
+ [source,java]
153
+ ----------
154
+ storeCredential("alice", result.getKeyId(), result.getPublicKeyCose());
155
+ ----------
156
+
157
+
158
+ === Authentication
159
+
160
+ Initiate an authentication ceremony:
161
+
162
+
163
+ [source,java]
164
+ ----------
165
+ AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder()
166
+ .username(Optional.of("alice"))
167
+ .build());
168
+ String json = jsonMapper.writeValueAsString(request);
169
+ return json;
170
+ ----------
171
+
172
+ Validate the response:
173
+
174
+ [source,java]
175
+ ----------
176
+ String responseJson = /* ... */;
177
+
178
+ PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc =
179
+ jsonMapper.readValue(responseJson, new TypeReference<PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>>() {
180
+ });
181
+
182
+ try {
183
+ AssertionResult result = rp.finishAssertion(FinishAssertionOptions.builder()
184
+ .request(request)
185
+ .response(pkc)
186
+ .build());
187
+
188
+ if (result.isSuccess()) {
189
+ return result.getUsername();
190
+ }
191
+ } catch (AssertionFailedException e) { /* ... */ }
192
+ throw new RuntimeException("Authentication failed");
193
+ ----------
194
+
195
+ For more detailed example usage, see
196
+ link:webauthn-server-demo[`webauthn-server-demo`] for a complete demo server.
197
+
198
+
199
+ == Architecture
200
+
201
+ The library tries to place as few requirements on the overall application
202
+ architecture as possible. For this reason it is stateless and free from side
203
+ effects, and does not directly interact with any database. This means it is
204
+ database agnostic and thread safe. The following diagram illustrates an example
205
+ architecture for an application using the library.
206
+
207
+ image::https://raw.githubusercontent.com/Yubico/java-webauthn-server/master/docs/img/demo-architecture.svg["Example application architecture",align="center"]
208
+
209
+ The application manages all state and database access, and communicates with the
210
+ library via POJO representations of requests and responses. The following
211
+ diagram illustrates the data flow during a WebAuthn registration or
212
+ authentication ceremony.
213
+
214
+ image::https://raw.githubusercontent.com/Yubico/java-webauthn-server/master/docs/img/demo-sequence-diagram.svg["WebAuthn ceremony sequence diagram",align="center"]
215
+
216
+ In this diagram, the *Client* is the user's browser and the application's
217
+ client-side scripts. The *Server* is the application and its business logic, the
218
+ *Library* is this library, and the *Users* database stores registered WebAuthn
219
+ credentials.
220
+
221
+ . The client requests to start the ceremony, for example by submitting a form.
222
+ The `username` may or may not be known at this point. For example, the user
223
+ might be requesting to create a new account, or we might be using
224
+ username-less authentication.
225
+
226
+ . If the user does not already have a
227
+ https://www.w3.org/TR/webauthn/#user-handle[user handle], the application
228
+ creates one in some application-specific way.
229
+
230
+ . The application may choose to authenticate the user with a password or the
231
+ like before proceeding.
232
+
233
+ . The application calls one of the library's "start" methods to generate a
234
+ parameter object to be passed to `navigator.credentials.create()` or `.get()`
235
+ on the client.
236
+
237
+ . The library generates a random challenge and an assortment of other arguments
238
+ depending on configuration set by the application.
239
+
240
+ . If the `username` is known, the library uses a read-only database adapter
241
+ provided by the application to look up the user's credentials.
242
+
243
+ . The returned list of https://www.w3.org/TR/webauthn/#credential-id[credential
244
+ IDs] is used to populate the
245
+ https://www.w3.org/TR/webauthn/#dom-publickeycredentialcreationoptions-excludecredentials[`excludeCredentials`]
246
+ or
247
+ https://www.w3.org/TR/webauthn/#dom-publickeycredentialrequestoptions-allowcredentials[`allowCredentials`]
248
+ parameter.
249
+
250
+ . The library returns a `request` object which can be serialized to JSON and
251
+ passed as the `publicKey` argument to `navigator.credentials.create()` or
252
+ `.get()`. For registration ceremonies this will be a
253
+ https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions[`PublicKeyCredentialCreationOptions`],
254
+ and for authentication ceremonies it will be a
255
+ https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions[`PublicKeyCredentialRequestOptions`].
256
+ The application stores the `request` in temporary storage.
257
+
258
+ . The application's client-side script runs `navigator.credentials.create()` or
259
+ `.get()` with `response` as the `publicKey` argument.
260
+
261
+ . The user confirms the operation and the client returns a
262
+ https://www.w3.org/TR/webauthn/#public-key-credential[`PublicKeyCredential`]
263
+ object `response` to the application.
264
+
265
+ . The application retrieves the `request` from temporary storage and passes
266
+ `request` and `response` to one of the library's "finish" methods to run the
267
+ response validation logic.
268
+
269
+ . The library verifies that the `response` contents - challenge, origin, etc. -
270
+ are valid.
271
+
272
+ . If this is an authentication ceremony, the library uses the database adapter
273
+ to look up the public key for the credential named in `response.id`.
274
+
275
+ . The database adapter returns the public key.
276
+
277
+ . The library verifies the authentication signature.
278
+
279
+ . The library returns a POJO representation of the result of the ceremony. For
280
+ registration ceremonies, this will include the credential ID and public key of
281
+ the new credential. The application may also opt in to also getting
282
+ information about the authenticator model and whether the authenticator
283
+ attestation is trusted. For authentication ceremonies, this will include the
284
+ username and user handle, the credential ID of the credential used, and the
285
+ new https://www.w3.org/TR/webauthn/#signature-counter[signature counter] value
286
+ for the credential.
287
+
288
+ . The application inspects the result object and takes any appropriate actions
289
+ as defined by its business logic.
18
290
19
- None.
291
+ . If the result is not satisfactory, the application reports failure to the
292
+ client.
20
293
294
+ . If the result is satisfactory, the application proceeds with storing the new
295
+ credential if this is a registration ceremony.
21
296
22
- === Example Usage
297
+ . If this is an authentication ceremony, the application updates the signature
298
+ counter stored in the database for the credential.
23
299
24
- See link:webauthn-server-demo[`webauthn-server-demo`] for a complete demo
25
- server, which stores authenticator registrations temporarily in memory.
300
+ . Finally, the application reports success and resumes its business logic.
26
301
27
302
28
- === Building
303
+ == Building
29
304
30
305
Use the included
31
306
https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle wrapper] to
0 commit comments