fix: proxy passkey bind through Management API
All checks were successful
CI / build (push) Successful in 2m17s
CI / docker (push) Successful in 1m29s

Logto's /api/my-account/ endpoints reject the opaque access token with
401 even though /api/verifications/ accepts it. The bind step now goes
through the SaaS backend which calls the Management API instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-27 18:23:41 +02:00
parent 2aa5100530
commit 4df6fc9e03
4 changed files with 35 additions and 15 deletions

View File

@@ -1,8 +1,10 @@
import { startRegistration } from '@simplewebauthn/browser';
import { api } from './client';
/**
* Logto Account API client for WebAuthn passkey registration.
* Calls Logto's Account API endpoints directly (same domain).
* The bind step goes through the SaaS backend (Management API).
*/
async function accountApi(
@@ -72,19 +74,10 @@ export async function registerPasskey(
const verifyData = await verifyRes.json();
const verifiedRecordId = verifyData.verificationRecordId;
// Step 5: Bind the passkey — requires logto-verification-id header for sensitive op
const bindRes = await accountApi(
'POST',
'/my-account/mfa-verifications',
token,
{
type: 'WebAuthn',
newIdentifierVerificationRecordId: verifiedRecordId,
},
{ 'logto-verification-id': identityVerificationId },
);
if (!bindRes.ok) {
const err = await bindRes.json().catch(() => ({}));
throw new Error(err.message || `Failed to bind passkey (${bindRes.status})`);
}
// Step 5: Bind via SaaS backend (Management API) — Logto's /api/my-account/
// rejects the opaque token, so we proxy through our backend.
await api.post('/account/mfa/webauthn/bind', {
verificationRecordId: verifiedRecordId,
identityVerificationId,
});
}