Skip to content

Commit 2f3323a

Browse files
committed
add more content
1 parent 5b65246 commit 2f3323a

File tree

1 file changed

+110
-15
lines changed

1 file changed

+110
-15
lines changed

README.md

Lines changed: 110 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,78 @@ interface with a webpage that will handle calling the WebAuthn API:
4949

5050
#### Setup
5151

52+
Configure your backend with a `WebAuthnManager` instance:
53+
54+
```swift
55+
app.webAuthn = WebAuthnManager(
56+
config: WebAuthnConfig(
57+
relyingPartyDisplayName: "My Fancy Web App",
58+
relyingPartyID: "example.com",
59+
relyingPartyOrigin: "https://example.com",
60+
timeout: 600
61+
)
62+
)
63+
```
5264

5365
#### Registration
5466

55-
1. A user wants to signup on a website using WebAuthn. The client makes a request to the backend which implements this
56-
library. On request the backend calls the `beginRegistration(user:)` method and sends the returned
57-
`PublicKeyCredentialCreationOptions` back to the client.
67+
Scenario: A user wants to signup on a website using WebAuthn.
68+
69+
##### Explanation
70+
71+
1. When tapping the "Register" button the client sends a request to
72+
the backend. The backend responds to this request with a call to `begingRegistration(user:)` which then returns a
73+
new `PublicKeyCredentialRequestOptions`. This must be send back to the client so it can pass it to
74+
`navigator.credentials.create()`.
5875

59-
2. The client passes the received `PublicKeyCredentialCreationOptions` via the WebAuthn API to
60-
`navigator.credentials.create()`. This in turn will prompt the user to create a new credential using an
61-
authenticator of their choice (TouchID, security keys, ...). The response must then be send back to the backend.
76+
2. Whatever `navigator.credentials.create()` returns will be send back to the backend, parsing it into
77+
`RegistrationCredential`.
78+
```swift
79+
let registrationCredential = try req.content.decode(RegistrationCredential.self)
80+
```
6281

63-
3. On request the backend calls the `finishRegistration(challenge:credentialCreationData:)` method with the previously
64-
generated challenge and the received authenticator response (from `navigator.credentials.create()`). If
65-
`finishRegistration` succeeds a new `Credential` object will be returned. This object should be persisted somewhere
66-
(e.g. a database) and linked to the user from step 1.
82+
3. Next the backend calls `finishRegistration(challenge:credentialCreationData:)` with the previously
83+
generated challenge and the received `RegistrationCredential`. If `finishRegistration` succeeds a new `Credential`
84+
object will be returned. This object contains information about the new credential, including an id and the generated public-key. Persist this data in e.g. a database and link the entry to the user.
85+
86+
##### Example implementation
87+
88+
```swift
89+
authSessionRoutes.get("makeCredential") { req -> PublicKeyCredentialCreationOptions in
90+
let user = try req.auth.require(User.self)
91+
let options = try req.webAuthn.beginRegistration(user: user)
92+
req.session.data["challenge"] = options.challenge
93+
return options
94+
}
95+
96+
authSessionRoutes.post("makeCredential") { req -> HTTPStatus in
97+
let user = try req.auth.require(User.self)
98+
guard let challenge = req.session.data["challenge"] else { throw Abort(.unauthorized) }
99+
let registrationCredential = try req.content.decode(RegistrationCredential.self)
100+
101+
let credential = try await req.webAuthn.finishRegistration(
102+
challenge: challenge,
103+
credentialCreationData: registrationCredential,
104+
// this is likely to be removed soon
105+
confirmCredentialIDNotRegisteredYet: { credentialID in
106+
try await queryCredentialWithUser(id: credentialID) == nil
107+
}
108+
)
109+
110+
try await WebAuthnCredential(from: credential, userID: user.requireID())
111+
.save(on: req.db)
112+
113+
return .ok
114+
}
115+
```
67116

68117
#### Authentication
69118

70-
1. A user wants to log in on a website using WebAuthn. When tapping the "Login" button the client send a request to
119+
Scenario: A user wants to log in on a website using WebAuthn.
120+
121+
##### Explanation
122+
123+
1. When tapping the "Login" button the client sends a request to
71124
the backend. The backend responds to this request with a call to `beginAuthentication()` which then in turn
72125
returns a new `PublicKeyCredentialRequestOptions`. This must be send back to the client so it can pass it to
73126
`navigator.credentials.get()`.
@@ -80,11 +133,53 @@ interface with a webpage that will handle calling the WebAuthn API:
80133
`finishAuthentication(credential:expectedChallenge:credentialPublicKey:credentialCurrentSignCount:)`.
81134
- The `credential` parameter expects the decoded `AuthenticationCredential`
82135
- The `expectedChallenge` parameter expects the challenge previously generated
83-
from `beginAuthentication()` (e.g. through a session).
84-
- Query the persisted credential from [Registration](####registration) using the credential id from the decoded
136+
from `beginAuthentication()` (obtained e.g. through a session).
137+
- Query the persisted credential from [Registration](#registration) using the credential id from the decoded
85138
`AuthenticationCredential`. Pass this credential in the `credentialPublicKey` parameter and it's sign count to
86139
`credentialCurrentSignCount`.
87140

88141
4. If `finishAuthentication` succeeds you can safely login the user linked to the credential! `finishAuthentication`
89-
will return a `VerifiedAuthentication` with the updated sign count and a few other information. Use this to
90-
update the credential in the database.
142+
will return a `VerifiedAuthentication` with the updated sign count and a few other information meant to be persisted.
143+
Use this to update the credential in the database.
144+
145+
##### Implementation example
146+
147+
```swift
148+
// this endpoint will be called on clicking "Login"
149+
authSessionRoutes.get("authenticate") { req -> PublicKeyCredentialRequestOptions in
150+
let options = try req.webAuthn.beginAuthentication()
151+
req.session.data["challenge"] = String.base64URL(fromBase64: options.challenge)
152+
return options
153+
}
154+
155+
// this endpoint will be called after the user used e.g. TouchID.
156+
authSessionRoutes.post("authenticate") { req -> HTTPStatus in
157+
guard let challenge = req.session.data["challenge"] else { throw Abort(.unauthorized) }
158+
let data = try req.content.decode(AuthenticationCredential.self)
159+
guard let credential = try await queryCredentialWithUser(id: data.id) else {
160+
throw Abort(.unauthorized)
161+
}
162+
163+
let verifiedAuthentication = try req.webAuthn.finishAuthentication(
164+
credential: data,
165+
expectedChallenge: challenge,
166+
credentialPublicKey: [UInt8](credential.publicKey.base64URLDecodedData!),
167+
credentialCurrentSignCount: 0
168+
)
169+
170+
req.auth.login(credential.user)
171+
172+
return .ok
173+
}
174+
```
175+
176+
## Credits
177+
178+
Swift WebAuthn is heavily inspired by existing WebAuthn libraries like [py_webauthn](https://github.com/duo-labs/py_webauthn) and [go-webauthn](https://github.com/go-webauthn/webauthn).
179+
180+
## Links
181+
182+
- [WebAuthn.io](https://webauthn.io/)
183+
- [WebAuthn guide](https://webauthn.guide/)
184+
- [WebAuthn Spec](https://w3c.github.io/webauthn/)
185+
- [CBOR.me](https://cbor.me/)

0 commit comments

Comments
 (0)