1
1
import { createCheckAuthorization } from '@clerk/shared/authorization' ;
2
- import { is4xxError } from '@clerk/shared/error' ;
2
+ import { ClerkWebAuthnError , is4xxError } from '@clerk/shared/error' ;
3
3
import { retry } from '@clerk/shared/retry' ;
4
+ import { isWebAuthnSupported as isWebAuthnSupportedOnWindow } from '@clerk/shared/webauthn' ;
4
5
import type {
5
6
ActJWTClaim ,
6
7
CheckAuthorization ,
@@ -25,7 +26,12 @@ import type {
25
26
} from '@clerk/types' ;
26
27
27
28
import { unixEpochToDate } from '../../utils/date' ;
28
- import { clerkInvalidStrategy } from '../errors' ;
29
+ import {
30
+ convertJSONToPublicKeyRequestOptions ,
31
+ serializePublicKeyCredentialAssertion ,
32
+ webAuthnGetCredential as webAuthnGetCredentialOnWindow ,
33
+ } from '../../utils/passkeys' ;
34
+ import { clerkInvalidStrategy , clerkMissingWebAuthnPublicKeyOptions } from '../errors' ;
29
35
import { eventBus , events } from '../events' ;
30
36
import { SessionTokenCache } from '../tokenCache' ;
31
37
import { BaseResource , PublicUserData , Token , User } from './internal' ;
@@ -150,6 +156,9 @@ export class Session extends BaseResource implements SessionResource {
150
156
default : factor . default ,
151
157
} as PhoneCodeConfig ;
152
158
break ;
159
+ case 'passkey' :
160
+ config = { } ;
161
+ break ;
153
162
default :
154
163
clerkInvalidStrategy ( 'Session.prepareFirstFactorVerification' , ( factor as any ) . strategy ) ;
155
164
}
@@ -171,17 +180,68 @@ export class Session extends BaseResource implements SessionResource {
171
180
attemptFirstFactorVerification = async (
172
181
attemptFactor : SessionVerifyAttemptFirstFactorParams ,
173
182
) : Promise < SessionVerificationResource > => {
183
+ let config ;
184
+ switch ( attemptFactor . strategy ) {
185
+ case 'passkey' : {
186
+ config = {
187
+ publicKeyCredential : JSON . stringify ( serializePublicKeyCredentialAssertion ( attemptFactor . publicKeyCredential ) ) ,
188
+ } ;
189
+ break ;
190
+ }
191
+ default :
192
+ config = { ...attemptFactor } ;
193
+ }
194
+
174
195
const json = (
175
196
await BaseResource . _fetch ( {
176
197
method : 'POST' ,
177
198
path : `/client/sessions/${ this . id } /verify/attempt_first_factor` ,
178
- body : { ...attemptFactor , strategy : attemptFactor . strategy } as any ,
199
+ body : { ...config , strategy : attemptFactor . strategy } as any ,
179
200
} )
180
201
) ?. response as unknown as SessionVerificationJSON ;
181
202
182
203
return new SessionVerification ( json ) ;
183
204
} ;
184
205
206
+ verifyWithPasskey = async ( ) : Promise < SessionVerificationResource > => {
207
+ const prepareResponse = await this . prepareFirstFactorVerification ( { strategy : 'passkey' } ) ;
208
+
209
+ const { nonce = null } = prepareResponse . firstFactorVerification ;
210
+
211
+ /**
212
+ * The UI should always prevent from this method being called if WebAuthn is not supported.
213
+ * As a precaution we need to check if WebAuthn is supported.
214
+ */
215
+ const isWebAuthnSupported = Session . clerk . __internal_isWebAuthnSupported || isWebAuthnSupportedOnWindow ;
216
+ const webAuthnGetCredential = Session . clerk . __internal_getPublicCredentials || webAuthnGetCredentialOnWindow ;
217
+
218
+ if ( ! isWebAuthnSupported ( ) ) {
219
+ throw new ClerkWebAuthnError ( 'Passkeys are not supported' , {
220
+ code : 'passkey_not_supported' ,
221
+ } ) ;
222
+ }
223
+
224
+ const publicKeyOptions = nonce ? convertJSONToPublicKeyRequestOptions ( JSON . parse ( nonce ) ) : null ;
225
+
226
+ if ( ! publicKeyOptions ) {
227
+ clerkMissingWebAuthnPublicKeyOptions ( 'get' ) ;
228
+ }
229
+
230
+ const { publicKeyCredential, error } = await webAuthnGetCredential ( {
231
+ publicKeyOptions,
232
+ conditionalUI : false ,
233
+ } ) ;
234
+
235
+ if ( ! publicKeyCredential ) {
236
+ throw error ;
237
+ }
238
+
239
+ return this . attemptFirstFactorVerification ( {
240
+ strategy : 'passkey' ,
241
+ publicKeyCredential,
242
+ } ) ;
243
+ } ;
244
+
185
245
prepareSecondFactorVerification = async (
186
246
params : SessionVerifyPrepareSecondFactorParams ,
187
247
) : Promise < SessionVerificationResource > => {
@@ -197,13 +257,13 @@ export class Session extends BaseResource implements SessionResource {
197
257
} ;
198
258
199
259
attemptSecondFactorVerification = async (
200
- params : SessionVerifyAttemptSecondFactorParams ,
260
+ attemptFactor : SessionVerifyAttemptSecondFactorParams ,
201
261
) : Promise < SessionVerificationResource > => {
202
262
const json = (
203
263
await BaseResource . _fetch ( {
204
264
method : 'POST' ,
205
265
path : `/client/sessions/${ this . id } /verify/attempt_second_factor` ,
206
- body : params as any ,
266
+ body : attemptFactor as any ,
207
267
} )
208
268
) ?. response as unknown as SessionVerificationJSON ;
209
269
0 commit comments