Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_tooltips/update.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See the [**Updates**](https://dashboard.clerk.com/~/updates) page in the Clerk Dashboard to see the available updates for your instance.
4 changes: 3 additions & 1 deletion docs/guides/configure/session-tasks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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) | [`<TaskChooseOrganization />`](/docs/reference/components/authentication/task-choose-organization) |
| [Force password reset](/docs/guides/secure/password-protection-and-rules#manually-set-a-password-as-compromised) | [`<TaskResetPassword />`](/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.
Expand Down
271 changes: 271 additions & 0 deletions docs/guides/development/custom-flows/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,274 @@ For instance, if you wish to inform a user at which absolute time they will be a
```
</Tab>
</Tabs>

### 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.

<Tabs items={["Next.js"]}>
<Tab>
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<ClerkAPIError[]>()
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 (
<>
<h1>Verify your email address</h1>
<form onSubmit={handleVerification}>
<label htmlFor="code">Enter your email verification code</label>
<input value={code} id="code" name="code" onChange={(e) => setCode(e.target.value)} />
<button type="submit">Verify</button>
</form>
</>
)
}

return (
<>
<form onSubmit={handleSubmit}>
<label htmlFor="email">Enter email address</label>
<input
value={email}
id="email"
name="email"
type="email"
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Continue</button>
</form>

{errors && (
<ul>
{errors.map((el, index) => (
<li key={index}>{el.longMessage}</li>
))}
</ul>
)}
</>
)
}

export default function SignInForm() {
const { isLoaded, signIn, setActive } = useSignIn()
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const [errors, setErrors] = React.useState<ClerkAPIError[]>()

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 (
<>
<h1>Sign in</h1>

<p>
Your password appears to have been compromised or it&apos;s no longer trusted and cannot
be used. Please use email code to continue.
</p>

<SignInWithEmailCode />
</>
)
}

// Display a form to capture the user's email and password
return (
<>
<h1>Sign in</h1>

<form onSubmit={(e) => handleSignInWithPassword(e)}>
<div>
<label htmlFor="email">Enter email address</label>
<input
onChange={(e) => setEmail(e.target.value)}
id="email"
name="email"
type="email"
value={email}
/>
</div>
<div>
<label htmlFor="password">Enter password</label>
<input
onChange={(e) => setPassword(e.target.value)}
id="password"
name="password"
type="password"
value={password}
/>
</div>
<button type="submit">Sign in</button>
</form>

{errors && (
<ul>
{errors.map((el, index) => (
<li key={index}>{el.longMessage}</li>
))}
</ul>
)}
</>
)
}
```
</Tab>
</Tabs>
6 changes: 3 additions & 3 deletions docs/guides/development/errors/frontend-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1343,12 +1343,12 @@ This variant also accepts a custom text to be displayed.

### <code><wbr />Form<wbr />Pwned<wbr />Password</code>

`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"
}
Expand Down
15 changes: 15 additions & 0 deletions docs/guides/secure/password-protection-and-rules.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 4 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3161,6 +3161,10 @@
"title": "`<TaskChooseOrganization />`",
"href": "/docs/reference/components/authentication/task-choose-organization"
},
{
"title": "`<TaskResetPassword />`",
"href": "/docs/reference/components/authentication/task-reset-password"
},
{
"title": "`<Waitlist />`",
"href": "/docs/reference/components/authentication/waitlist"
Expand Down
Loading