Docs

Error handling

When using the signUp() or signIn() functions, proper error handling can help you provide your users with useful feedback.

Example

A user is attempting to sign up with your application, but they are attempting to use a password that has been found in an online data breach. This will return a 422 error code with the message: "Password has been found in an online data breach. For account safety, please use a different password."

import { useState } from "react";
import { useSignUp } from "@clerk/clerk-react";
import { ClerkAPIError } from "@clerk/types";
import { isClerkAPIResponseError } from "@clerk/clerk-react/errors";

function Example() {
  const [emailAddress, setEmailAddress] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<ClerkAPIError[]>();

  const { isLoaded, signUp, setActive } = useSignUp();

  const attemptSignUp = async (e: React.FormEvent) => {
    e.preventDefault();

    // clear any errors that may have occured during previous form submission
    setErrors(undefined);

    if (!isLoaded || !signUp) return;

    try {
      const result = await signUp.create({ emailAddress, password});
      if (result.status === 'complete') {
        await setActive({ session: result.createdSessionId });
        return;
      }
      if (result.status === 'abandoned') {
        const error = { code: result.status, message: "The sign-up attempt will be abandoned if it was started more than 24 hours previously." }
        setErrors([error]);
        return;
      }
      if (result.status === 'missing_requirements') {
        const error = { code: result.status, message: "A requirement from the email, phone, username settings is missing." }
        setErrors([error]);
        return;
      }
    } catch(err: unknown) {
      if (isClerkAPIResponseError(err)) setErrors(err.errors);
      return; 
    }
  };

  return (
    <form onSubmit={attemptSignUp}>
      <div>
        <label htmlFor="email">Email address</label>
        <input 
          id="email" 
          type="email" 
          value={emailAddress} 
          onChange={(e) => {setEmailAddress(e.target.value)}} 
        />
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input 
          id="password" 
          type="password" 
          value={password} 
          onChange={(e) => {setPassword(e.target.value)}} 
        />
      </div>

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

      <button type="submit">Sign Up</button>
    </form>
  );
}
const { client } = window.Clerk;

const signUp = await client.signUp.create({
  identifier: "example@email.com",
  password: "Tester01!",
})
  .then((res) => console.log("response", res))
  .catch((err) => setError(err.errors[0].message));

Errors returned from the signIn() function are handled in a similar way:

import { useState } from "react";
import { useSignIn } from "@clerk/clerk-react";
import { ClerkAPIError } from "@clerk/types";
import { isClerkAPIResponseError } from "@clerk/clerk-react/errors";

function Example() {
  const [emailAddress, setEmailAddress] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<ClerkAPIError[]>();

  const { isLoaded, signIn, setActive } = useSignIn();

  const attemptSignIn = async (e: React.FormEvent) => {
    e.preventDefault();

    // clear any errors that may have occured during previous form submission
    setErrors(undefined);

    if (!isLoaded || !signIn) return;

    try {
      const result = await signIn.create({ identifier: emailAddress, password});
      if (result.status === 'complete') {
        await setActive({ session: result.createdSessionId });
        return;
      }
      if (result.status === 'needs_identifier')  {
        const error = { code: result.status, message: "The authentication identifier hasn't been provided." }
        setErrors([error]);
        return;
      }
      if (result.status === 'needs_first_factor')  {
        const error = { code: result.status, message: "First factor verification for the provided identifier needs to be prepared and verified." }
        setErrors([error]);
        return;
      }
      if (result.status === 'needs_second_factor')  {
        const error = { code: result.status, message: "Second factor verification (2FA) for the provided identifier needs to be prepared and verified." }
        setErrors([error]);
        return;
      }
      if (result.status === 'needs_new_password')  {
        const error = { code: result.status, message: "New password hasn't been provided." }
        setErrors([error]);
        return;
      }
    } catch(err: unknown) {
      if (isClerkAPIResponseError(err)) setErrors(err.errors);
      return;
    }
  };

  return (
    <form onSubmit={attemptSignIn}>
      <div>
        <label htmlFor="email">Email address</label>
        <input 
          id="email" 
          type="email" 
          value={emailAddress} 
          onChange={(e) => {setEmailAddress(e.target.value)}} 
        />
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input 
          id="password" 
          type="password" 
          value={password} 
          onChange={(e) => {setPassword(e.target.value)}} 
        />
      </div>

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

      <button type="submit">Sign In</button>
    </form>
  );
}
const { client } = window.Clerk;

const signIn = await client.signIn.create({
  identifier: emailAddress,
  password,
  })    
  .then((result) => console.log("result", result))
  .catch((err) => console.error("error", err.errors[0].message));

Special error cases

User locked

If you have Account Lockout enabled on your instance and the user reaches the maximum allowed attempts (see list of relevant actions here), you will receive an HTTP status of 403 (Forbidden) and the following error payload:

{
  "errors": [
    {
      "message": "Account locked",
      "long_message": "Your account is locked. You will be able to try again in 30 minutes. For more information, please contact support.",
      "code": "user_locked",
      "meta": {
        "lockout_expires_in_seconds": 1800
      }
    }
  ]
}

lockout_expires_in_seconds represents the time remaining until the user is able to attempt authentication again. In the above example, 1800 seconds (or 30 minutes) are left until they are able to retry, as of the current moment.

The admin might have configured e.g. a 45-minute lockout duration. Thus, 15 minutes after one has been locked, 30 minutes will still remain until the lockout lapses.

You can opt to render the error message returned as-is or format the supplied lockout_expires_in_seconds value as per your liking in your own custom error message.

For instance, if you wish to inform a user at which absolute time they will be able to try again, you could add the remaining seconds to the current time and format the resulting timestamp.

if (errors[0].code === 'user_locked') {
    // Get the current date and time
    let currentDate = new Date();

    // Add the remaining seconds until lockout expires
    currentDate.setSeconds(currentDate.getSeconds() + errors[0].meta.lockout_expires_in_seconds);

    // Format the resulting date and time into a human-readable string
    const lockoutExpiresAt = currentDate.toLocaleString();

    // Do something with lockoutExpiresAt
    console.log('Your account is locked, you will be able to try again at ' + lockoutExpiresAt);
}

Feedback

What did you think of this content?