From 9d0835ca06e87cbe63aa4b3bdee00453347e6429 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Mon, 3 Aug 2026 22:22:01 -0700 Subject: [PATCH 1/3] feat(backend): add removePassword to User API --- .changeset/remove-user-password.md | 5 +++ .../backend/src/api/__tests__/UserApi.test.ts | 33 ++++++++++++++++ packages/backend/src/api/endpoints/UserApi.ts | 39 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 .changeset/remove-user-password.md diff --git a/.changeset/remove-user-password.md b/.changeset/remove-user-password.md new file mode 100644 index 00000000000..c27da816c35 --- /dev/null +++ b/.changeset/remove-user-password.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': minor +--- + +Add `clerkClient.users.removePassword(userId, params?)` to remove a user's password through the Backend API. Existing sessions remain active by default; pass `{ signOutOfOtherSessions: true }` to revoke them. diff --git a/packages/backend/src/api/__tests__/UserApi.test.ts b/packages/backend/src/api/__tests__/UserApi.test.ts index abeb42bf361..bb3d7587282 100644 --- a/packages/backend/src/api/__tests__/UserApi.test.ts +++ b/packages/backend/src/api/__tests__/UserApi.test.ts @@ -176,4 +176,37 @@ describe('UserAPI', () => { expect(response.publicMetadata).toEqual({ replaced: true }); }); }); + + describe('removePassword', () => { + it('calls POST /users/{id}/remove_password without a request body by default', async () => { + const postHandler = vi.fn(async ({ request }: { request: Request }) => { + expect(await request.text()).toBe(''); + return HttpResponse.json(mockUserResponse); + }); + + server.use(http.post('https://api.clerk.test/v1/users/user_123/remove_password', validateHeaders(postHandler))); + + const response = await apiClient.users.removePassword('user_123'); + + expect(postHandler).toHaveBeenCalledTimes(1); + expect(response.id).toBe('user_123'); + }); + + it('passes signOutOfOtherSessions in the request body', async () => { + const postHandler = vi.fn(async ({ request }: { request: Request }) => { + expect(await request.json()).toEqual({ sign_out_of_other_sessions: true }); + return HttpResponse.json(mockUserResponse); + }); + + server.use(http.post('https://api.clerk.test/v1/users/user_123/remove_password', validateHeaders(postHandler))); + + await apiClient.users.removePassword('user_123', { signOutOfOtherSessions: true }); + + expect(postHandler).toHaveBeenCalledTimes(1); + }); + + it('requires a user ID', async () => { + await expect(apiClient.users.removePassword('')).rejects.toThrow('A valid resource ID is required.'); + }); + }); }); diff --git a/packages/backend/src/api/endpoints/UserApi.ts b/packages/backend/src/api/endpoints/UserApi.ts index eddcc8414f7..8618229d1da 100644 --- a/packages/backend/src/api/endpoints/UserApi.ts +++ b/packages/backend/src/api/endpoints/UserApi.ts @@ -366,6 +366,12 @@ export type VerifyPasswordParams = { password: string; }; +/** @inline */ +export type RemovePasswordParams = { + /** When set to `true`, all of the user's active sessions are revoked after their password is removed. Defaults to `false`. */ + signOutOfOtherSessions?: boolean; +}; + /** @generateWithEmptyComment */ export type VerifyTOTPParams = { /** The ID of the user to verify the TOTP for. */ @@ -697,6 +703,39 @@ export class UserAPI extends AbstractAPI { }); } + /** + * Removes the password credential from the given user. This is a privileged operation and does not require the user's current password. Password removal is allowed even when the user has no other sign-in method configured. + * + * By default, existing sessions remain active. Set `signOutOfOtherSessions` to `true` to revoke sessions active when the request is processed. + * @param userId - The ID of the user whose password to remove. + * @param params - Options for the request. + * @returns The updated [`User`](https://clerk.com/docs/reference/backend/types/backend-user). + * @example + * ### Keep existing sessions active + * + * ```ts + * const user = await clerkClient.users.removePassword('user_123'); + * ``` + * + * @example + * ### Revoke existing sessions + * + * ```ts + * const user = await clerkClient.users.removePassword('user_123', { + * signOutOfOtherSessions: true, + * }); + * ``` + */ + public async removePassword(userId: string, params: RemovePasswordParams = {}) { + this.requireId(userId); + + return this.request({ + method: 'POST', + path: joinPaths(basePath, userId, 'remove_password'), + bodyParams: params, + }); + } + /** Check that the user's password matches the supplied input. Useful for custom auth flows and re-verification. */ public async verifyPassword(params: VerifyPasswordParams) { const { userId, password } = params; From 7d4cca071d87caf85017925a107879e09864f6b2 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 4 Aug 2026 21:42:20 -0700 Subject: [PATCH 2/3] docs(backend): clarify password removal changeset --- .changeset/remove-user-password.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/remove-user-password.md b/.changeset/remove-user-password.md index c27da816c35..88e23f4db4e 100644 --- a/.changeset/remove-user-password.md +++ b/.changeset/remove-user-password.md @@ -2,4 +2,4 @@ '@clerk/backend': minor --- -Add `clerkClient.users.removePassword(userId, params?)` to remove a user's password through the Backend API. Existing sessions remain active by default; pass `{ signOutOfOtherSessions: true }` to revoke them. +Add `clerkClient.users.removePassword(userId, params?)` to remove a user's password through the Backend API. Password removal is allowed even when the user has no alternate sign-in method configured. Existing sessions remain active by default; pass `{ signOutOfOtherSessions: true }` to revoke them. From e539b4669c3cdba89a8fb8a0bbdb2e28d152c9d7 Mon Sep 17 00:00:00 2001 From: Josh Rowley Date: Tue, 4 Aug 2026 21:44:08 -0700 Subject: [PATCH 3/3] fix(backend): declare removePassword return type --- packages/backend/src/api/endpoints/UserApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/api/endpoints/UserApi.ts b/packages/backend/src/api/endpoints/UserApi.ts index 8618229d1da..8b6e673c02b 100644 --- a/packages/backend/src/api/endpoints/UserApi.ts +++ b/packages/backend/src/api/endpoints/UserApi.ts @@ -726,7 +726,7 @@ export class UserAPI extends AbstractAPI { * }); * ``` */ - public async removePassword(userId: string, params: RemovePasswordParams = {}) { + public async removePassword(userId: string, params: RemovePasswordParams = {}): Promise { this.requireId(userId); return this.request({