diff --git a/docs/_tooltips/update.mdx b/docs/_tooltips/update.mdx new file mode 100644 index 0000000000..142c24765e --- /dev/null +++ b/docs/_tooltips/update.mdx @@ -0,0 +1 @@ +See the [**Updates**](https://dashboard.clerk.com/~/updates) page in the Clerk Dashboard to see the available updates for your instance. diff --git a/docs/guides/configure/session-tasks.mdx b/docs/guides/configure/session-tasks.mdx index 5e4ca70ec7..7834ffefb6 100644 --- a/docs/guides/configure/session-tasks.mdx +++ b/docs/guides/configure/session-tasks.mdx @@ -13,7 +13,8 @@ The following table lists the available tasks and their corresponding keys. | Setting | Key | Description | | - | - | - | -| [Allow Personal Accounts](https://dashboard.clerk.com/~/organizations-settings) | `choose-organization` | Disabled by default when enabling Organizations. When disabled, users are required to choose an Organization after authenticating. When enabled, users can choose a Personal Account instead of an Organization. | +| [Allow Personal Accounts](https://dashboard.clerk.com/~/organizations-settings) | `choose-organization` | [Disabled by default for instances created after August 22, 2025](!update). When disabled, users are required to choose an Organization after authenticating. When enabled, users can choose a Personal Account instead of an Organization. | +| [Force password reset](/docs/guides/secure/password-protection-and-rules#manually-set-a-password-as-compromised) | `reset-password` | [Enabled by default for instances created after December 8, 2025](!update). When enabled, the user is required to reset their password on their next sign-in if their password is marked as compromised. If your instance is older than December 8, 2025, you will need to update your instance to the **Reset password session task** update. | ## Session states @@ -34,6 +35,7 @@ The following table lists the available tasks and their corresponding components | Name | Component | | - | - | | [Personal accounts disabled (default)](/docs/guides/organizations/configure#enable-organizations) | [``](/docs/reference/components/authentication/task-choose-organization) | +| [Force password reset](/docs/guides/secure/password-protection-and-rules#manually-set-a-password-as-compromised) | [``](/docs/reference/components/authentication/task-reset-password) | > [!IMPORTANT] > Personal accounts being disabled by default was released on 08-22-2025. Applications created before this date will not be able to see the **Allow Personal Accounts** setting, because Personal Accounts were enabled by default. diff --git a/docs/guides/development/custom-flows/error-handling.mdx b/docs/guides/development/custom-flows/error-handling.mdx index 25c68890b8..98ffb63ee8 100644 --- a/docs/guides/development/custom-flows/error-handling.mdx +++ b/docs/guides/development/custom-flows/error-handling.mdx @@ -271,3 +271,274 @@ For instance, if you wish to inform a user at which absolute time they will be a ``` + +### Password compromised + +If you have marked a user's password as compromised and the user has another way to identify themselves, such as an email address (so they can use email code or magic link), or a phone number (so they can receive an SMS code), you will receive an HTTP status of `422 (Unprocessable Entity)` and the following error payload: + +```json +{ + "errors": [ + { + "long_message": "Your password may be compromised. To protect your account, please continue with an alternative sign-in method. You will be required to reset your password after signing in.", + "code": "form_password_compromised", + "meta": { + "name": "param" + } + } + ] +} +``` + +When a user password is marked as compromised, they will not be able to sign in with their compromised password, so you should prompt them to sign-in with another method. If they do not have any other identification methods to sign-in, e.g if they only have username and password, they will be signed in but they will be required to reset their password. + +> [!WARNING] +> If your instance is older than December 8, 2025, you will need to update your instance to the **Reset password session task** update. + + + + This example is written for Next.js App Router but it can be adapted for any React-based framework. + + ```tsx {{ filename: 'app/sign-in/page.tsx' }} + 'use client' + + import * as React from 'react' + import { useSignIn } from '@clerk/nextjs' + import { useRouter } from 'next/navigation' + import { ClerkAPIError, EmailCodeFactor, SignInFirstFactor } from '@clerk/types' + import { isClerkAPIResponseError } from '@clerk/nextjs/errors' + + const SignInWithEmailCode = () => { + const { isLoaded, signIn, setActive } = useSignIn() + const [errors, setErrors] = React.useState() + const [verifying, setVerifying] = React.useState(false) + const [email, setEmail] = React.useState('') + const [code, setCode] = React.useState('') + const router = useRouter() + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + + if (!isLoaded && !signIn) return null + + try { + // Start the sign-in process using the email code method + const { supportedFirstFactors } = await signIn.create({ + identifier: email, + }) + + // Filter the returned array to find the 'email_code' entry + const isEmailCodeFactor = (factor: SignInFirstFactor): factor is EmailCodeFactor => { + return factor.strategy === 'email_code' + } + const emailCodeFactor = supportedFirstFactors?.find(isEmailCodeFactor) + + if (emailCodeFactor) { + // Grab the emailAddressId + const { emailAddressId } = emailCodeFactor + + // Send the OTP code to the user + await signIn.prepareFirstFactor({ + strategy: 'email_code', + emailAddressId, + }) + + // Set verifying to true to display second form + // and capture the OTP code + setVerifying(true) + } + } catch (err) { + // See https://clerk.com/docs/guides/development/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + } + } + + async function handleVerification(e: React.FormEvent) { + e.preventDefault() + + if (!isLoaded && !signIn) return null + + try { + // Use the code provided by the user and attempt verification + const signInAttempt = await signIn.attemptFirstFactor({ + strategy: 'email_code', + code, + }) + + // If verification was completed, set the session to active + // and redirect the user + if (signInAttempt.status === 'complete') { + await setActive({ + session: signInAttempt.createdSessionId, + navigate: async ({ session }) => { + if (session?.currentTask) { + // Check for tasks and navigate to custom UI to help users resolve them + // See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks + console.log(session?.currentTask) + return + } + + router.push('/') + }, + }) + } else { + // If the status is not complete, check why. User may need to + // complete further steps. + console.error(signInAttempt) + } + } catch (err) { + // See https://clerk.com/docs/guides/development/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + } + } + + if (verifying) { + return ( + <> +

Verify your email address

+
+ + setCode(e.target.value)} /> + +
+ + ) + } + + return ( + <> +
+ + setEmail(e.target.value)} + /> + +
+ + {errors && ( +
    + {errors.map((el, index) => ( +
  • {el.longMessage}
  • + ))} +
+ )} + + ) + } + + export default function SignInForm() { + const { isLoaded, signIn, setActive } = useSignIn() + const [email, setEmail] = React.useState('') + const [password, setPassword] = React.useState('') + const [errors, setErrors] = React.useState() + + const router = useRouter() + + // Handle the submission of the sign-in form + const handleSignInWithPassword = async (e: React.FormEvent) => { + e.preventDefault() + + // Clear any errors that may have occurred during previous form submission + setErrors(undefined) + + if (!isLoaded) { + return + } + + // Start the sign-in process using the email and password provided + try { + const signInAttempt = await signIn.create({ + identifier: email, + password, + }) + + // If sign-in process is complete, set the created session as active + // and redirect the user + if (signInAttempt.status === 'complete') { + await setActive({ + session: signInAttempt.createdSessionId, + navigate: async ({ session }) => { + if (session?.currentTask) { + // Check for tasks and navigate to custom UI to help users resolve them + // See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks + console.log(session?.currentTask) + return + } + + router.push('/') + }, + }) + } else { + // If the status is not complete, check why. User may need to + // complete further steps. + console.error(JSON.stringify(signInAttempt, null, 2)) + } + } catch (err) { + if (isClerkAPIResponseError(err)) setErrors(err.errors) + console.error(JSON.stringify(err, null, 2)) + } + } + + if (errors && errors[0].code === 'form_password_compromised') { + return ( + <> +

Sign in

+ +

+ Your password appears to have been compromised or it's no longer trusted and cannot + be used. Please use email code to continue. +

+ + + + ) + } + + // Display a form to capture the user's email and password + return ( + <> +

Sign in

+ +
handleSignInWithPassword(e)}> +
+ + setEmail(e.target.value)} + id="email" + name="email" + type="email" + value={email} + /> +
+
+ + setPassword(e.target.value)} + id="password" + name="password" + type="password" + value={password} + /> +
+ +
+ + {errors && ( +
    + {errors.map((el, index) => ( +
  • {el.longMessage}
  • + ))} +
+ )} + + ) + } + ``` +
+
diff --git a/docs/guides/development/errors/frontend-api.mdx b/docs/guides/development/errors/frontend-api.mdx index e93b5616fb..d50715630a 100644 --- a/docs/guides/development/errors/frontend-api.mdx +++ b/docs/guides/development/errors/frontend-api.mdx @@ -1343,12 +1343,12 @@ This variant also accepts a custom text to be displayed. ### FormPwnedPassword -`FormPwnedPassword` signifies an error when the chosen password has been found in the pwned list +`FormPwnedPassword` signifies an error when the chosen password has been found in [pwned's collection of compromised passwords](https://haveibeenpwned.com/Passwords). ```json {{ filename: 'Status Code: 422' }} { - "shortMessage": "", - "code": "form_password_pwned", + "longMessage": "Your password may be compromised. To protect your account, please continue with an alternative sign-in method. You will be required to reset your password after signing in.", + "code": "form_password_compromised", "meta": { "name": "param" } diff --git a/docs/guides/secure/password-protection-and-rules.mdx b/docs/guides/secure/password-protection-and-rules.mdx index a1217d1627..a31a29ba8f 100644 --- a/docs/guides/secure/password-protection-and-rules.mdx +++ b/docs/guides/secure/password-protection-and-rules.mdx @@ -49,3 +49,18 @@ For users that set an average/weak password that complies with your organization > [!NOTE] > OWASP recommends providing feedback to users on the strength of their password and offering suggestions for improvement. This can help users create stronger passwords and improve the overall security of the application. + +## Manually set a password as compromised + +Clerk provides a way to manually set a password as compromised. This is useful for blocking passwords in the case that: + +- The password has recently been added to the compromised password database. +- The user was able to set a compromised password because protection was off at the time. + +To manually set a user's password as compromised: + +1. In the Clerk Dashboard, navigate to [**Users**](https://dashboard.clerk.com/~/users) page and select the user you want to mark as compromised. You'll be redirected to the user's settings. +1. In the **Password** section, if a password is set, select the three dots icon and select **Set password compromised**. A modal will appear asking you to confirm the action. Complete the instructions. + +> [!IMPORTANT] +> Setting a user's password as compromised will prevent the user from signing in until they reset their password. If you are implementing [custom authentication flows](!custom-flow), you will need to handle the compromised password flow by yourself. See [Error handling](/docs/guides/development/custom-flows/error-handling#password-compromised) for more information. diff --git a/docs/manifest.json b/docs/manifest.json index bff85c8ec1..010f2c0d68 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -3161,6 +3161,10 @@ "title": "``", "href": "/docs/reference/components/authentication/task-choose-organization" }, + { + "title": "``", + "href": "/docs/reference/components/authentication/task-reset-password" + }, { "title": "``", "href": "/docs/reference/components/authentication/waitlist" diff --git a/docs/reference/components/authentication/task-reset-password.mdx b/docs/reference/components/authentication/task-reset-password.mdx new file mode 100644 index 0000000000..9e056c1a67 --- /dev/null +++ b/docs/reference/components/authentication/task-reset-password.mdx @@ -0,0 +1,299 @@ +--- +title: '`` component' +description: Clerk's component renders a UI for resolving the `reset-password` task. +sdk: js-frontend, nextjs, react, react-router, remix, tanstack-react-start +--- + +![The \ component renders a UI for resolving the reset-password session task.](/docs/images/ui-components/task-reset-password.png){{ style: { maxWidth: '460px' } }} + +The `` component renders a UI for resolving the `reset-password` [session task](!session-tasks). + +> [!IMPORTANT] +> The `` component cannot render when a user doesn't have current session tasks. + + + ## Example + + The following example demonstrates how to host the `` component on a custom page. + + + ```tsx {{ filename: 'app/layout.tsx', mark: [7] }} + import { ClerkProvider } from '@clerk/nextjs' + + export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ) + } + ``` + + The `` component must be used in conjunction with the `` component. See the [dedicated guide on how to self-host the `` component](/docs/nextjs/guides/development/custom-sign-in-or-up-page). + + ```tsx {{ filename: 'app/onboarding/reset-password/page.tsx' }} + import { TaskResetPassword } from '@clerk/nextjs' + + export default function Page() { + return + } + ``` + + + + ```tsx {{ filename: 'index.tsx', mark: [17] }} + import React from 'react' + import ReactDOM from 'react-dom/client' + import App from './App.tsx' + import { ClerkProvider } from '@clerk/clerk-react' + + // Import your Publishable Key + const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY + + if (!PUBLISHABLE_KEY) { + throw new Error('Add your Clerk Publishable Key to the .env file') + } + + ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , + ) + ``` + + ```jsx {{ filename: 'src/onboarding/reset-password.tsx' }} + import { TaskResetPassword } from '@clerk/clerk-react' + + const ResetPasswordPage = () => + + export default ResetPasswordPage + ``` + + + + > [!NOTE] + > To see the full `root.tsx` setup you need for Clerk with React Router, see the [React Router quickstart](/docs/react-router/getting-started/quickstart). + + ```tsx {{ filename: 'app/root.tsx', mark: [6] }} + import { ClerkProvider } from '@clerk/react-router' + import { Outlet } from 'react-router' + + export default function App() { + return ( + + + + ) + } + ``` + + The `` component must be used in conjunction with the `` component. See the [dedicated guide on how to self-host the `` component](/docs/react-router/guides/development/custom-sign-in-or-up-page). + + ```tsx {{ filename: 'app/routes/onboarding/reset-password.tsx' }} + import { TaskResetPassword } from '@clerk/react-router' + + export default function ResetPasswordPage() { + return + } + ``` + + + + > [!NOTE] + > To see the full `__root.tsx` setup you need for Clerk with TanStack React Start, see the [TanStack React Start quickstart](/docs/tanstack-react-start/getting-started/quickstart). + + ```tsx {{ filename: 'src/routes/__root.tsx', mark: [7] }} + import * as React from 'react' + import { HeadContent, Scripts } from '@tanstack/react-router' + import { ClerkProvider } from '@clerk/tanstack-react-start' + + function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + + + {children} + + + + + ) + } + ``` + + The `` component must be used in conjunction with the `` component. See the [dedicated guide on how to self-host the `` component](/docs/tanstack-react-start/guides/development/custom-sign-in-or-up-page). + + ```tsx {{ filename: 'src/routes/onboarding/reset-password.tsx' }} + import { TaskResetPassword } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/onboarding/reset-password')({ + component: ResetPasswordPage, + }) + + function ResetPasswordPage() { + return + } + ``` + + + + + ## Usage with JavaScript + + The following methods available on an instance of the [`Clerk`](/docs/reference/javascript/clerk) class are used to render and control the `` component: + + - [`mountTaskResetPassword()`](#mount-task-reset-password) + - [`unmountTaskResetPassword()`](#unmount-task-reset-password) + + The following examples assume that you have followed the [quickstart](/docs/js-frontend/getting-started/quickstart) in order to add Clerk to your JavaScript application. + + ### `mountTaskResetPassword()` + + Render the `` component to an HTML `
` element. + + ```typescript + function mountTaskResetPassword(node: HTMLDivElement, props?: TaskResetPasswordProps): void + ``` + + #### `mountTaskResetPassword()` params + + + - `node ` + - [`HTMLDivElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement) + + The `
` element used to render in the `` component + + --- + + - `props?` + - [`TaskResetPasswordProps`](#properties) + + The properties to pass to the `` component. + + + #### `mountTaskResetPassword()` usage + + ```typescript {{ filename: 'main.ts', mark: [26] }} + import { Clerk } from '@clerk/clerk-js' + + const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY + + const clerk = new Clerk(pubKey) + await clerk.load() + + if (clerk.isSignedIn) { + // Mount user button component + document.getElementById('signed-in').innerHTML = ` +
+ ` + + const userbuttonDiv = document.getElementById('user-button') + + clerk.mountUserButton(userbuttonDiv) + } else if (clerk.session?.currentTask) { + switch (clerk.session.currentTask.key) { + case 'reset-password': { + document.getElementById('app').innerHTML = ` +
+ ` + + const taskResetPasswordDiv = document.getElementById('task-reset-password') + + clerk.mountTaskResetPassword(taskResetPasswordDiv) + } + } + } + ``` + + ### `unmountTaskResetPassword()` + + Unmount and run cleanup on an existing `` component instance. + + ```typescript + function unmountTaskResetPassword(node: HTMLDivElement): void + ``` + + #### `unmountTaskResetPassword()` params + + + - `node ` + - [`HTMLDivElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement) + + The container `
` element with a rendered `` component instance + + + #### `unmountTaskResetPassword()` usage + + ```typescript {{ filename: 'main.ts', mark: [30] }} + import { Clerk } from '@clerk/clerk-js' + + const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY + + const clerk = new Clerk(pubKey) + await clerk.load() + + if (clerk.isSignedIn) { + // Mount user button component + document.getElementById('signed-in').innerHTML = ` +
+ ` + + const userbuttonDiv = document.getElementById('user-button') + + clerk.mountUserButton(userbuttonDiv) + } else if (clerk.session?.currentTask) { + switch (clerk.session.currentTask.key) { + case 'reset-password': { + document.getElementById('app').innerHTML = ` +
+ ` + + const taskResetPasswordDiv = document.getElementById('task-reset-password') + + clerk.mountTaskResetPassword(taskResetPasswordDiv) + + // ... + + clerk.unmountTaskResetPassword(taskResetPasswordDiv) + } + } + } + ``` + + +## Properties + +All props are optional. + + + - `redirectUrlComplete` + - `string` + + The full URL or path to navigate to after successfully completing all tasks. + + --- + + - `appearance` + - [Appearance](/docs/guides/customizing-clerk/appearance-prop/overview) | undefined + + Optional object to style your components. Will only affect [Clerk components](/docs/reference/components/overview) and not [Account Portal](/docs/guides/customizing-clerk/account-portal) pages. + + +## Customization + +To learn about how to customize Clerk components, see the [customization documentation](/docs/guides/customizing-clerk/appearance-prop/overview). + +If Clerk's prebuilt components don't meet your specific needs or if you require more control over the logic, you can rebuild the existing Clerk flows using the Clerk API. For more information, see the [custom flow guides](/docs/guides/development/custom-flows/overview). diff --git a/public/images/ui-components/task-reset-password.png b/public/images/ui-components/task-reset-password.png new file mode 100644 index 0000000000..360139105f Binary files /dev/null and b/public/images/ui-components/task-reset-password.png differ