Skip to content

Commit ef39958

Browse files
authored
Merge pull request #1023 from supertokens/fix/webauth-mng-endpoints-session-requirements
fix: Change how WebAuthn credential management endpoints handle session requirements
2 parents e5c2475 + 9f83106 commit ef39958

File tree

9 files changed

+82
-69
lines changed

9 files changed

+82
-69
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
## [23.0.1] - 2025-07-31
11+
1012
- Updated FDI support to 4.2
1113
- Added `recipeUserId` in the WebAuthn list credentials response
14+
- Prevent removal of WebAuthn credentials unless all session claims are satisfied
15+
- Change how sessions are fetched before listing, removing and adding WebAuthn credentials
1216

1317
## [23.0.0] - 2025-07-21
1418

lib/build/recipe/webauthn/api/implementation.js

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/recipe/webauthn/api/listCredentials.js

Lines changed: 3 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/recipe/webauthn/api/registerCredential.js

Lines changed: 7 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/recipe/webauthn/api/removeCredential.js

Lines changed: 7 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ts/recipe/webauthn/api/implementation.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { getRecoverAccountLink } from "../utils";
2020
import { logDebugMessage } from "../../../logger";
2121
import { RecipeLevelUser } from "../../accountlinking/types";
2222
import { getUser } from "../../..";
23+
import MultiFactorAuth from "../../multifactorauth";
24+
import MultiFactorAuthRecipe from "../../multifactorauth/recipe";
2325

2426
export default function getAPIImplementation(): APIInterface {
2527
return {
@@ -183,7 +185,7 @@ export default function getAPIImplementation(): APIInterface {
183185
recipeId: "webauthn",
184186
email,
185187
},
186-
factorIds: ["webauthn"],
188+
factorIds: [MultiFactorAuth.FactorIds.WEBAUTHN],
187189
isSignUp: true,
188190
isVerified: isFakeEmail(email),
189191
signInVerifiesLoginMethod: false,
@@ -366,7 +368,7 @@ export default function getAPIImplementation(): APIInterface {
366368
recipeId,
367369
email,
368370
},
369-
factorIds: [recipeId],
371+
factorIds: [MultiFactorAuth.FactorIds.WEBAUTHN],
370372
isSignUp: false,
371373
authenticatingUser: authenticatingUser?.user,
372374
isVerified,
@@ -1017,6 +1019,15 @@ export default function getAPIImplementation(): APIInterface {
10171019
"The credentials are incorrect. Please make sure you are using the correct credentials. (ERR_CODE_025)",
10181020
};
10191021

1022+
const mfaInstance = MultiFactorAuthRecipe.getInstance();
1023+
if (mfaInstance) {
1024+
await MultiFactorAuth.assertAllowedToSetupFactorElseThrowInvalidClaimError(
1025+
session,
1026+
MultiFactorAuth.FactorIds.WEBAUTHN,
1027+
userContext
1028+
);
1029+
}
1030+
10201031
const generatedOptions = await options.recipeImplementation.getGeneratedOptions({
10211032
webauthnGeneratedOptionsId,
10221033
tenantId,
@@ -1058,6 +1069,13 @@ export default function getAPIImplementation(): APIInterface {
10581069
};
10591070
},
10601071
removeCredentialPOST: async function ({ webauthnCredentialId, options, userContext, session }) {
1072+
const mfaInstance = MultiFactorAuthRecipe.getInstance();
1073+
if (mfaInstance) {
1074+
await session.assertClaims([
1075+
MultiFactorAuth.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth(),
1076+
]);
1077+
}
1078+
10611079
const removeCredentialResponse = await options.recipeImplementation.removeCredential({
10621080
webauthnCredentialId,
10631081
recipeUserId: session.getRecipeUserId().getAsString(),

lib/ts/recipe/webauthn/api/listCredentials.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515

1616
import { send200Response } from "../../../utils";
1717
import { APIInterface, APIOptions } from "..";
18-
import STError from "../error";
1918
import { UserContext } from "../../../types";
20-
import { AuthUtils } from "../../../authUtils";
19+
import Session from "../../session";
2120

2221
export default async function listCredentialsAPI(
2322
apiImplementation: APIInterface,
@@ -29,13 +28,12 @@ export default async function listCredentialsAPI(
2928
return false;
3029
}
3130

32-
const session = await AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, undefined, userContext);
33-
if (session === undefined) {
34-
throw new STError({
35-
type: STError.BAD_INPUT_ERROR,
36-
message: "A valid session is required to list credentials",
37-
});
38-
}
31+
const session = await Session.getSession(
32+
options.req,
33+
options.res,
34+
{ overrideGlobalClaimValidators: () => [], sessionRequired: true },
35+
userContext
36+
);
3937

4038
const result = await apiImplementation.listCredentialsGET({
4139
options,

lib/ts/recipe/webauthn/api/registerCredential.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
import { send200Response } from "../../../utils";
1717
import { validateWebauthnGeneratedOptionsIdOrThrowError, validateCredentialOrThrowError } from "./utils";
1818
import { APIInterface, APIOptions } from "..";
19-
import STError from "../error";
2019
import { UserContext } from "../../../types";
21-
import { AuthUtils } from "../../../authUtils";
20+
import Session from "../../session";
2221

2322
export default async function registerCredentialAPI(
2423
apiImplementation: APIInterface,
@@ -30,20 +29,19 @@ export default async function registerCredentialAPI(
3029
return false;
3130
}
3231

32+
const session = await Session.getSession(
33+
options.req,
34+
options.res,
35+
{ overrideGlobalClaimValidators: () => [], sessionRequired: true },
36+
userContext
37+
);
38+
3339
const requestBody = await options.req.getJSONBody();
3440
const webauthnGeneratedOptionsId = validateWebauthnGeneratedOptionsIdOrThrowError(
3541
requestBody.webauthnGeneratedOptionsId
3642
);
3743
const credential = validateCredentialOrThrowError(requestBody.credential);
3844

39-
const session = await AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, undefined, userContext);
40-
if (session === undefined) {
41-
throw new STError({
42-
type: STError.BAD_INPUT_ERROR,
43-
message: "A valid session is required to register a credential",
44-
});
45-
}
46-
4745
const result = await apiImplementation.registerCredentialPOST({
4846
credential,
4947
webauthnGeneratedOptionsId,

lib/ts/recipe/webauthn/api/removeCredential.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { send200Response } from "../../../utils";
1717
import { APIInterface, APIOptions } from "..";
1818
import STError from "../error";
1919
import { UserContext } from "../../../types";
20-
import { AuthUtils } from "../../../authUtils";
20+
import Session from "../../session";
2121

2222
export default async function removeCredentialAPI(
2323
apiImplementation: APIInterface,
@@ -29,6 +29,13 @@ export default async function removeCredentialAPI(
2929
return false;
3030
}
3131

32+
const session = await Session.getSession(
33+
options.req,
34+
options.res,
35+
{ overrideGlobalClaimValidators: () => [], sessionRequired: true },
36+
userContext
37+
);
38+
3239
const requestBody = await options.req.getJSONBody();
3340
const webauthnCredentialId = requestBody.webauthnCredentialId;
3441
if (webauthnCredentialId === undefined) {
@@ -38,14 +45,6 @@ export default async function removeCredentialAPI(
3845
});
3946
}
4047

41-
const session = await AuthUtils.loadSessionInAuthAPIIfNeeded(options.req, options.res, undefined, userContext);
42-
if (session === undefined) {
43-
throw new STError({
44-
type: STError.BAD_INPUT_ERROR,
45-
message: "A valid session is required to remove a credential",
46-
});
47-
}
48-
4948
const result = await apiImplementation.removeCredentialPOST({
5049
webauthnCredentialId,
5150
options,

0 commit comments

Comments
 (0)