@@ -97,7 +97,13 @@ private static Mock<UserManager<PocoUser>> SetupUserManager(PocoUser user)
9797 return manager ;
9898 }
9999
100- private static SignInManager < PocoUser > SetupSignInManager ( UserManager < PocoUser > manager , HttpContext context , ILogger logger = null , IdentityOptions identityOptions = null , IAuthenticationSchemeProvider schemeProvider = null )
100+ private static SignInManager < PocoUser > SetupSignInManager (
101+ UserManager < PocoUser > manager ,
102+ HttpContext context ,
103+ ILogger logger = null ,
104+ IdentityOptions identityOptions = null ,
105+ IAuthenticationSchemeProvider schemeProvider = null ,
106+ IPasskeyHandler < PocoUser > passkeyHandler = null )
101107 {
102108 var contextAccessor = new Mock < IHttpContextAccessor > ( ) ;
103109 contextAccessor . Setup ( a => a . HttpContext ) . Returns ( context ) ;
@@ -107,7 +113,16 @@ private static SignInManager<PocoUser> SetupSignInManager(UserManager<PocoUser>
107113 options . Setup ( a => a . Value ) . Returns ( identityOptions ) ;
108114 var claimsFactory = new UserClaimsPrincipalFactory < PocoUser , PocoRole > ( manager , roleManager . Object , options . Object ) ;
109115 schemeProvider = schemeProvider ?? new MockSchemeProvider ( ) ;
110- var sm = new SignInManager < PocoUser > ( manager , contextAccessor . Object , claimsFactory , options . Object , null , schemeProvider , new DefaultUserConfirmation < PocoUser > ( ) ) ;
116+ passkeyHandler = passkeyHandler ?? Mock . Of < IPasskeyHandler < PocoUser > > ( ) ;
117+ var sm = new SignInManager < PocoUser > (
118+ manager ,
119+ contextAccessor . Object ,
120+ claimsFactory ,
121+ options . Object ,
122+ null ,
123+ schemeProvider ,
124+ new DefaultUserConfirmation < PocoUser > ( ) ,
125+ passkeyHandler ) ;
111126 sm . Logger = logger ?? NullLogger < SignInManager < PocoUser > > . Instance ;
112127 return sm ;
113128 }
@@ -339,6 +354,38 @@ public async Task ExternalSignInRequiresVerificationIfNotBypassed(bool bypass)
339354 auth . Verify ( ) ;
340355 }
341356
357+ [ Fact ]
358+ public async Task CanPasskeySignIn ( )
359+ {
360+ // Setup
361+ var user = new PocoUser { UserName = "Foo" } ;
362+ var passkey = new UserPasskeyInfo ( null , null , null , default , 0 , null , false , false , false , null , null ) ;
363+ var assertionResult = PasskeyAssertionResult . Success ( passkey , user ) ;
364+ var passkeyHandler = new Mock < IPasskeyHandler < PocoUser > > ( ) ;
365+ passkeyHandler
366+ . Setup ( h => h . PerformAssertionAsync ( It . IsAny < PasskeyAssertionContext < PocoUser > > ( ) ) )
367+ . Returns ( Task . FromResult ( assertionResult ) ) ;
368+ var manager = SetupUserManager ( user ) ;
369+ manager
370+ . Setup ( m => m . SetPasskeyAsync ( user , passkey ) )
371+ . Returns ( Task . FromResult ( IdentityResult . Success ) )
372+ . Verifiable ( ) ;
373+ var context = new DefaultHttpContext ( ) ;
374+ var auth = MockAuth ( context ) ;
375+ SetupSignIn ( context , auth , user . Id , isPersistent : false , loginProvider : null ) ;
376+ var helper = SetupSignInManager ( manager . Object , context , passkeyHandler : passkeyHandler . Object ) ;
377+
378+ // Act
379+ var passkeyRequestOptions = new PasskeyRequestOptions ( userId : user . Id , "<some-options>" ) ;
380+ var signInResult = await helper . PasskeySignInAsync ( credentialJson : "<some-passkey>" , passkeyRequestOptions ) ;
381+
382+ // Assert
383+ Assert . True ( assertionResult . Succeeded ) ;
384+ Assert . Same ( SignInResult . Success , signInResult ) ;
385+ manager . Verify ( ) ;
386+ auth . Verify ( ) ;
387+ }
388+
342389 private class GoodTokenProvider : AuthenticatorTokenProvider < PocoUser >
343390 {
344391 public override Task < bool > ValidateAsync ( string purpose , string token , UserManager < PocoUser > manager , PocoUser user )
0 commit comments