refactor: move passkey enrollment to sign-in UI via Experience API
Remove the SaaS backend proxy approach for passkey registration (Account API binding, Management API proxy, password modal in PasskeySection). Instead, offer passkey enrollment natively during sign-in via Logto's Experience API — the correct architectural layer. Sign-in flow: when Logto returns MFA enrollment available (422), show a "Secure your account" screen with Register passkey / Set up later. Uses Experience API WebAuthn registration endpoints. Works for all users (SaaS and future server users) since the sign-in UI is shared. PasskeySection in account settings now only manages existing passkeys (list/rename/delete) and directs users to register during sign-in. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -103,17 +103,6 @@ public class AccountController {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// --- WebAuthn bind (proxied via Management API since Account API /my-account/ returns 401) ---
|
||||
|
||||
record WebAuthnBindRequest(String verificationRecordId, String identityVerificationId) {}
|
||||
|
||||
@PostMapping("/mfa/webauthn/bind")
|
||||
public ResponseEntity<Void> bindWebAuthn(@AuthenticationPrincipal Jwt jwt,
|
||||
@RequestBody WebAuthnBindRequest request) {
|
||||
accountService.bindWebAuthnPasskey(jwt.getSubject(), request.verificationRecordId(), request.identityVerificationId());
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// --- MFA Preference ---
|
||||
|
||||
@PostMapping("/mfa/method-preference")
|
||||
|
||||
@@ -150,10 +150,6 @@ public class AccountService {
|
||||
}
|
||||
}
|
||||
|
||||
public void bindWebAuthnPasskey(String userId, String verificationRecordId, String identityVerificationId) {
|
||||
logtoClient.bindWebAuthnMfa(userId, verificationRecordId, identityVerificationId);
|
||||
}
|
||||
|
||||
// --- Passkeys ---
|
||||
|
||||
public List<PasskeyCredential> listPasskeys(String userId) {
|
||||
|
||||
@@ -13,8 +13,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Syncs Logto configuration on startup: enables the Account Center
|
||||
* and ensures MFA factors (including WebAuthn) match the vendor policy.
|
||||
* Syncs Logto configuration on startup: ensures MFA factors
|
||||
* (including WebAuthn) match the vendor policy.
|
||||
*/
|
||||
@Component
|
||||
public class LogtoStartupConfig {
|
||||
@@ -32,7 +32,6 @@ public class LogtoStartupConfig {
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onStartup() {
|
||||
logtoClient.enableAccountCenter();
|
||||
syncMfaFactors();
|
||||
}
|
||||
|
||||
|
||||
@@ -653,40 +653,6 @@ public class LogtoManagementClient {
|
||||
}
|
||||
}
|
||||
|
||||
/** Bind a WebAuthn passkey to a user via the Management API. */
|
||||
public void bindWebAuthnMfa(String userId, String verificationRecordId, String identityVerificationId) {
|
||||
if (!isAvailable()) throw new IllegalStateException("Logto not configured");
|
||||
restClient.post()
|
||||
.uri(config.getLogtoEndpoint() + "/api/users/" + userId + "/mfa-verifications")
|
||||
.header("Authorization", "Bearer " + getAccessToken())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Map.of("type", "WebAuthn", "verificationRecordId", verificationRecordId))
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
}
|
||||
|
||||
/** Enable the Account Center API for MFA management (WebAuthn passkey registration). */
|
||||
public void enableAccountCenter() {
|
||||
if (!isAvailable()) return;
|
||||
try {
|
||||
restClient.patch()
|
||||
.uri(config.getLogtoEndpoint() + "/api/account-center")
|
||||
.header("Authorization", "Bearer " + getAccessToken())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Map.of(
|
||||
"enabled", true,
|
||||
"fields", Map.of(
|
||||
"mfa", "Edit"
|
||||
)
|
||||
))
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
log.info("Account Center enabled for MFA management");
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to enable Account Center: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Update user custom data (partial merge). Used for mfa_method_preference. */
|
||||
public void updateUserCustomData(String userId, Map<String, Object> customData) {
|
||||
if (!isAvailable()) return;
|
||||
|
||||
Reference in New Issue
Block a user