From e57c59df95e65b83f08b0d60938adce3864b6b4d Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:31:47 -0400 Subject: [PATCH 1/8] add info about email links to /sign-up-sign-in-options doc --- .../configuration/sign-up-sign-in-options.mdx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/authentication/configuration/sign-up-sign-in-options.mdx b/docs/authentication/configuration/sign-up-sign-in-options.mdx index 4ccbbd392c..6b0c76d358 100644 --- a/docs/authentication/configuration/sign-up-sign-in-options.mdx +++ b/docs/authentication/configuration/sign-up-sign-in-options.mdx @@ -125,7 +125,20 @@ If a country is disabled, then phone numbers starting with the corresponding cou ### Email link -When the **Email verification link** option is selected as an authentication strategy, users will receive an email message with a link that can be visited in order to complete the authentication process. Email links can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to their profile. Email links work on any device. There's no constraint on where the link will be opened. For example, a user might try to sign in from their desktop browser, but open the link from their mobile phone. +When the **Email verification link** option is selected as an authentication strategy, users will receive an email message with a link that can be visited in order to complete the authentication process. Email links can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to their profile. + +As a security measure, email links expire after 10 minutes to prevent the use of potentially compromised stale links. + +#### Require the same device and browser + +By default, email links can work on any device. There's no constraint on where the link can be opened. For example, a user could try to sign in from their desktop browser, but open the link from their mobile phone. In this scenario, _the user's sign in would be completed on the desktop browser from which it was initiated, not the mobile phone where it was verified_. As a result, the user would be signed in on their desktop, not their phone. + +To configure this setting: + +1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username). +1. In the navigation sidebar, select **User & Authentication > Email, Phone, and Username**. +1. In the **Authentication strategies** section, next to **Email verification link**, select the settings cog icon. +1. Enable or disable the **Require the same device and browser** setting. ## Verification methods From a507caf4a19a6ba7eb8b7718d6cfb09e2160f8eb Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:31:59 -0400 Subject: [PATCH 2/8] update copy and organization of custom flow doc --- docs/custom-flows/email-links.mdx | 1151 +++++------------------------ 1 file changed, 169 insertions(+), 982 deletions(-) diff --git a/docs/custom-flows/email-links.mdx b/docs/custom-flows/email-links.mdx index 265052567b..9a27262367 100644 --- a/docs/custom-flows/email-links.mdx +++ b/docs/custom-flows/email-links.mdx @@ -1,1056 +1,243 @@ --- -title: Email links -description: Learn how to authenticate or verify users with email links. +title: Build a custom flow for handling email links +description: Learn how to build a custom flow using Clerk's API to handle email links for sign-up, sign-in, and email address verification. --- -# Email links - -Clerk supports passwordless authentication with email links, which lets users sign in and sign up without having to remember a password. During sign in or registration, users will be asked to enter their email address. They will receive an email message with a link that can be clicked in order to complete the authentication process. - -This one-click, link-based verification method is often referred to as an "email link". The process is similar to sending a one-time code to your users but skipping the part where they have to come back to your app and enter the code. - -As a form of passwordless authentication, email links arguably provide greater security and a better user experience than traditional passwords. Since there are fewer steps involved in every authentication attempt, the user experience is better than one-time codes. However, email links are not without their downsides, and often still boil down to the email provider's "knowledge-based factor" instead of yours. - -Email links are the default passwordless authentication strategy when using Clerk. They can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to their profile. - -Your users will still be able to choose an alternative authentication (or verification) method even after they've clicked the email link they received in their inbox. Email links are simply the default authentication method for email address-based, passwordless authentication in Clerk. - -> [!NOTE] -> Looking for one-time code (OTP) authentication? Check out our [one-time code authentication](/docs/custom-flows/email-sms-otp) guide. +> [!WARNING] +> This guide is for users who want to build a _custom_ user interface using the Clerk API. To use a _prebuilt_ UI, you should use Clerk's [Account Portal pages](/docs/customization/account-portal//overview) or [prebuilt components](/docs/components/overview). -## Email link flow +[Email links](/docs/authentication/configuration/sign-up-sign-in-options#email-link) can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to their profile. -Email links can be used to easily authenticate users or verify their email addresses. Clerk will take care of the plumbing and allow you to offer a seamless experience to your users. The email link flow looks like this: +The email link flow works as follows: 1. The user enters their email address and asks for an email link. -1. Your application waits for the verification result. 1. Clerk sends an email to the user, containing a link to the verification URL. -1. The user clicks the email link. This can happen on a different device from where they entered their email address if the [**Require the same device or browser**](/docs/security/email-link-protection) setting is _off_. -1. Clerk will verify the user's identity and advance any sign-in or sign-up attempt that might be in progress. In case the verification fails, Clerk will inform the user. -1. Your user will now be signed in on the device that initiated the sign in. +1. The user visits the email link. This can happen on the same device where they entered their email address, or on a different device. +1. Clerk will verify the user's identity and advance any sign-up or sign-in attempt that might be in progress. +1. If the verification is successful, the user is authenticated or their email address is verified, depending on the reason for the email link. -If you would like email links work on any device, make sure the [**Require the same device or browser**](/docs/security/email-link-protection) setting is _off_. When this setting is disabled, there's no constraint on where the link can be opened. For example, a user could try to sign in from their desktop browser, but open the link from their mobile phone. In this scenario, _the user's sign in would be completed on the desktop browser from which it was initiated, not the mobile phone where it was verified_. As a result, the user would be signed in on their desktop, not their phone. +This guide will demonstrate how to use Clerk's API to build a custom flow for handling email links. It will cover the following scenarios: -As an additional security measure, we expire email links after a while. This way, we can guard against cases where a stale link might be compromised. From a user experience perspective, the email link flow is supposed to be nearly synchronous. Don't worry, your users will have plenty of time to complete the flow before the email link expires. +- [Sign up](#sign-up-flow) +- [Sign in](#sign-in-flow) +- [Verify a new email address](#email-address-verification-flow) -Clerk provides a highly flexible API that allows you to hook into any of the above steps while abstracting away all the complexities of an email link-based authentication or verification flow. +{/* TODO: Update these Steps when the Steps component can accept other headings. As of right now, Steps can only accept H3s. */} -We take care of the boring stuff, like efficient polling, secure session management, and different device authentication so you can focus on your application code. + + ### Enable email link authentication -## Before you start + To allow your users to sign up or sign in using email links, you first need configure the appropriate settings in the Clerk Dashboard. -- You need to create a Clerk Application in your [Clerk Dashboard](https://dashboard.clerk.com/). For more information, check out our [Set up your application](/docs/quickstarts/setup-clerk) guide. -- You need to install the correct SDK for your application. You can find steps on how to do so through Clerk's [quickstart](/docs/quickstarts/overview) guides. + 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username). + 1. In the navigation sidebar, select **User & Authentication > Email, Phone, and Username**. + 1. In the **Contact information** section, **Email address** should be enabled. + 1. In the **Username** section, ensure that **Username** is not required, or else the `create()` method will require a username to be passed in the params. If you would like to use usernames, you must handle collecting the username in your custom flow. + 1. In the **Authentication strategies** section, toggle on **Email verification link**. + 1. Keep this page open as you will need to enable email link verification in the next step. -## Set up email link authentication in Your Clerk application + ### Enable email link verification -Email link authentication can be configured through the Clerk Dashboard. Go to **User & Authentication > [Email, Phone, and Username](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username)**. In the **Authentication factors** section of this page, choose **Email verification link** as the authentication factor. + [**Verification methods**](/docs/authentication/configuration/sign-up-sign-in-options#verification-methods) are different from [**Authentication strategies**](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategies). **Authentication strategies** are used for authenticating a user, such as when they are signing in to your application. **Verification methods** are used for verifying a user's identifier, such as an email address upon initial sign-up or when updating their profile. -Don't forget that you also need to make sure you've configured your application instance to request the user's email address. Users can receive email links only via email messages. Make sure you toggle on **Email address** under the **Contact information** section. + To allow your users to verify their email addresses using email links, configure the following settings: -If you click on the **Settings cog** icon next to **Email address**, the email address configuration screen will pop open. You can toggle on **Require** if you want to make sure that all users have an email address associated with their profile. + 1. On this same page in the Clerk Dashboard, next to **Email address**, select the settings cog icon. A modal will open. + 1. Under **Verification methods**, enable the **Email verification link** option. For the sake of this guide, uncheck the box for **Email verification code**. + 1. Select **Continue** to save your changes. -You can also find the **Verification methods** section on this screen. Here, you can toggle on **Email verification link** if you would like to allow your users to verify their email with an email link. You can also toggle on **Email verification code** if you would like to allow your users to verify their email with a one-time passcode. + ### Create the flows -> [!NOTE] -> **Verification methods** are different from **Authentication strategies**. **Verification methods** are used for verifying a user's identifier, such as an email address upon initial sign-up or when updating their profile. **Authentication strategies** are used for authenticating a user, such as when they are signing in to your application. + The flow for handling email links is the same across all scenarios. You must: -### Enable email link authentication using the Clerk API + 1. Initiate the sign-up, sign-in, or email verification process by collecting the user's email address. + 1. Prepare the email address verification, which sends an email link to the given address. + 1. Handle the email link verification result accordingly. + - If the email address verification is successful, set the newly created session as the active session. + - If the verification failed or the email link has expired, inform the user. -In case one of the above integration methods doesn't cover your needs, you can make use of lower-level commands and create a completely custom email link authentication flow. + ### Sign-up flow -> [!WARNING] -> You still need to configure your instance in order to enable email link authentication, as described at the top of this guide. - -#### Sign up using a custom flow (Clerk API) - -Registration with email links follows a set of steps that require users to enter their email address as an authentication identifier and click on a link that's delivered to them via email message. + In the following example, the user is prompted to enter their email address and then click a button to sign up using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. -If you would like to allow the sign-up process to be completed on a different device, make sure the [**Require the same device or browser**](/docs/security/email-link-protection) setting is _off_. When this setting is disabled, users can enter their email address in their desktop browser, but click the sign-up email link from their mobile phone. The user's email address will still be verified and registration will proceed on the device from which the sign up was initiated. + ### Sign-in flow -Let's see all the steps involved in more detail. + In the following example, the user is prompted to enter their email address and then click a button to sign in using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. -1. Initiate the sign-up process by collecting the user's identifier. It must be their email address. -1. Start the email link verification flow. There are two parts to the flow: + ### Email address verification flow -- Prepare a verification for the email address by sending an email with an email link to the user. -- Wait until the email link is clicked. This is a polling behavior that can be canceled at any time. + In the following example, the user is prompted to enter their email address and then click a button to verify their email address using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. -3. Handle the email link verification result accordingly. Note that even if the email link is clicked on a different device/browser than the one which initiated the flow, the session will be created only on the original device. + + ```jsx {{ prettier: false }} + import React from "react"; + import { useUser, useEmailLink } from "@clerk/nextjs"; -- The verification was successful so you need to continue with the sign-up flow. -- The verification failed or the email link has expired. + // A page where users can add a new email address. + function NewEmailPage() { + const [email, setEmail] = React.useState(''); + const [emailAddress, setEmailAddress] = React.useState(null); + const [verified, setVerified] = React.useState(false); -Clerk provides a highly flexible API that allows you to hook into any of the above steps while abstracting away all the complexities of an email link-based sign-up flow. + const { user } = useUser(); - - ```jsx {{ prettier: false }} - import React from "react"; - import { useRouter } from "next/router"; - import { - EmailLinkErrorCode, - isEmailLinkError, - useClerk, - useSignUp - } from "@clerk/nextjs"; - - // pages/sign-up.jsx - // Render the sign up form. - // Collect user's email address and send an email link with which - // they can sign up. - function SignUp() { - const [emailAddress, setEmailAddress] = React.useState(""); - const [expired, setExpired] = React.useState(false); - const [verified, setVerified] = React.useState(false); - const router = useRouter(); - const { signUp, isLoaded, setActive } = useSignUp(); - - if (!isLoaded) { - return null; - } - - const { startEmailLinkFlow, cancelEmailLinkFlow } = - signUp.createEmailLinkFlow(); - - async function submit(e) { - e.preventDefault(); - setExpired(false); - setVerified(false); - - // Start the sign up flow, by collecting - // the user's email address. - await signUp.create({ emailAddress }); - - // Start the email link flow. - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - // su will hold the updated sign up object. - const su = await startEmailLinkFlow({ - redirectUrl: "https://your-app.domain.com/verification", - }); - - // Check the verification result. - const verification = su.verifications.emailAddress; - if (verification.verifiedFromTheSameClient()) { - setVerified(true); - // If you're handling the verification result from - // another route/component, you should return here. - // See the component as an - // example below. - // If you want to complete the flow on this tab, - // don't return. Check the sign up status instead. - return; - } else if (verification.status === "expired") { - setExpired(true); + async function submit(e) { + e.preventDefault(); + const res = await user.createEmailAddress({ email }); + setEmailAddress(res); } - if (su.status === "complete") { - // Sign up is complete, we have a session. - // Navigate to the after sign up URL. - setActive({ - session: su.createdSessionId, - beforeEmit: () => router.push("/after-sign-up-path"), - }); - return; + if (emailAddress && !verified) { + return ( + setVerified(true)} + /> + ); } - } - - if (expired) { - return ( -
Email link has expired
- ); - } - if (verified) { return ( -
Signed in on other tab
+
+ setEmail(e.target.value)} + /> +
); } - return ( -
- setEmailAddress(e.target.value)} - /> - -
- ); - } - - // pages/verification.jsx - // Handle email link verification results. This is - // the final step in the email link flow. - function Verification() { - const [ - verificationStatus, - setVerificationStatus, - ] = React.useState("loading"); + // A page which verifies email addresses with email links. + function VerifyWithEmailLink({ + emailAddress, + onVerify, + }) { + const { startEmailLinkFlow } = useEmailLink(emailAddress); - const { handleEmailLinkVerification } = useClerk(); + React.useEffect(() => { + verify(); + }, []); - React.useEffect(() => { async function verify() { - try { - await handleEmailLinkVerification({ - redirectUrl: "https://redirect-to-pending-sign-up", - redirectUrlComplete: "https://redirect-when-sign-up-complete", - }); - // If we're not redirected at this point, it means - // that the flow has completed on another device. - setVerificationStatus("verified"); - } catch (err) { - // Verification has failed. - let status = "failed"; - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { - status = "expired"; - } - setVerificationStatus(status); - } - } - verify(); - }, []); - - if (verificationStatus === "loading") { - return
Loading...
- } - - if (verificationStatus === "failed") { - return ( -
Email link verification failed
- ); - } - - if (verificationStatus === "expired") { - return ( -
Email link expired
- ); - } - - return ( -
- Successfully signed up. Return to the original tab to continue. -
- ); - } - ``` - - ```jsx {{ prettier: false }} - import React from "react"; - import { - BrowserRouter as Router, - Routes, - Route, - useNavigate, - } from 'react-router-dom'; - import { - ClerkProvider, - ClerkLoaded, - EmailLinkErrorCode, - isEmailLinkError, - UserButton, - useClerk, - useSignUp, - SignedOut, - SignedIn, - } from '@clerk/clerk-react'; - - const publishableKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY; - - function App() { - return ( - - - - {/* Root path shows sign up page. */} - - - - - - - - - } - /> - - {/* Define a /verification route that handles email link result */} - - - - } - /> - - - - ); - } - - // Render the sign up form. - // Collect user's email address and send an email link with which - // they can sign up. - function SignUpEmailLink() { - const [emailAddress, setEmailAddress] = React.useState(""); - const [expired, setExpired] = React.useState(false); - const [verified, setVerified] = React.useState(false); - const navigate = useNavigate(); - const { signUp, isLoaded, setActive } = useSignUp(); - - if (!isLoaded) { - return null; - } - - const { startEmailLinkFlow, cancelEmailLinkFlow } = - signUp.createEmailLinkFlow(); - - async function submit(e) { - e.preventDefault(); - setExpired(false); - setVerified(false); - - // Start the sign up flow, by collecting - // the user's email address. - await signUp.create({ emailAddress }); - - // Start the email link flow. - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - // su will hold the updated sign up object. - const su = await startEmailLinkFlow({ - redirectUrl: "https://your-app.domain.com/verification", - }); - - // Check the verification result. - const verification = su.verifications.emailAddress; - if (verification.verifiedFromTheSameClient()) { - setVerified(true); - // If you're handling the verification result from - // another route/component, you should return here. - // See the component as an - // example below. - // If you want to complete the flow on this tab, - // don't return. Check the sign up status instead. - return; - } else if (verification.status === "expired") { - setExpired(true); - } - - if (su.status === "complete") { - // Sign up is complete, we have a session. - // Navigate to the after sign up URL. - setActive({ - session: su.createdSessionId, - beforeEmit: () => navigate("/after-sign-up-path"), + // Start the email link flow. + // Pass your app URL that users will be navigated + // when they click the email link from their + // email inbox. + const res = await startEmailLinkFlow({ + redirectUrl: "https://redirect-from-email-email-link", }); - return; - } - } - - if (expired) { - return ( -
Email link has expired
- ); - } - - if (verified) { - return ( -
Signed in on other tab
- ); - } - - return ( -
- setEmailAddress(e.target.value)} - /> - -
- ); - } - // Handle email link verification results. This is - // the final step in the email link flow. - function EmailLinkVerification() { - const [ - verificationStatus, - setVerificationStatus, - ] = React.useState("loading"); - - const { handleEmailLinkVerification } = useClerk(); - - React.useEffect(() => { - async function verify() { - try { - await handleEmailLinkVerification({ - redirectUrl: "https://redirect-to-pending-sign-up", - redirectUrlComplete: "https://redirect-when-sign-up-complete", - }); - // If we're not redirected at this point, it means - // that the flow has completed on another device. - setVerificationStatus("verified"); - } catch (err) { - // Verification has failed. - let status = "failed"; - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { - status = "expired"; - } - setVerificationStatus(status); + // res will hold the updated EmailAddress object. + if (res.verification.status === "verified") { + onVerify(); + } else { + // act accordingly } } - verify(); - }, []); - - if (verificationStatus === "loading") { - return
Loading...
- } - - if (verificationStatus === "failed") { - return ( -
Email link verification failed
- ); - } - if (verificationStatus === "expired") { return ( -
Email link expired
+
+ Waiting for verification... +
); } + ``` - return ( -
- Successfully signed up. Return to the original tab to continue. -
- ); - } + ```jsx {{ prettier: false }} + import React from "react"; + import { useUser, useEmailLink } from "@clerk/clerk-react"; - export default App; - ``` + // A page where users can add a new email address. + function NewEmailPage() { + const [email, setEmail] = React.useState(''); + const [emailAddress, setEmailAddress] = React.useState(null); + const [verified, setVerified] = React.useState(false); - ```js {{ prettier: false }} - const signUp = window.Clerk.client.signUp; - const { - startEmailLinkFlow, - cancelEmailLinkFlow, - } = signUp.createEmailLinkFlow(); + const { user } = useUser(); - const res = await startEmailLinkFlow({ - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - redirectUrl: "https://redirect-from-email-email-link" - }); - if (res.status === "completed") { - // sign up completed - } else { - // sign up still pending - } - // Cleanup - cancelEmailLinkFlow(); - ``` -
- -#### Sign in using a custom flow (Clerk API) - -Signing users into your application is probably the most popular use case for email links. Users enter their email address and then click on a link that's delivered to them via email message in order to sign in. - -If you would like to allow the sign-in process to be completed on a different device, make sure the [**Require the same device or browser**](/docs/security/email-link-protection) setting is _off_. When this setting is disabled, users can enter their email address in their desktop browser, but click the sign-in email link from their mobile phone. The user's email address will still be verified and authentication will proceed on the device from which the sign in was initiated. - -Let's see all the steps involved in more detail. - -1. Initiate the sign-in process, by collecting the user's authentication identifier. It must be their email address. -1. Start the email link verification flow. There are two parts to the flow: - -- Prepare a verification for the email address by sending an email with an email link to the user. -- Wait until the email link is clicked. This is a polling behavior that can be canceled at any time. - -3. Handle the email link verification result accordingly. Note that even if the email link is clicked on a different device/browser than the one which initiated the flow, the session will be created only on the original device. - -- The verification was successful so you need to continue with the sign-in flow. -- The verification failed or the email link has expired. - -Clerk provides a highly flexible API that allows you to hook into any of the above steps, while abstracting away all the complexities of an email link based sign-in flow. - - - ```jsx {{ prettier: false }} - import React from "react"; - import { useRouter } from "next/router"; - import { - EmailLinkErrorCode, - isEmailLinkError, - useClerk, - useSignIn, - } from "@clerk/nextjs"; - - // pages/sign-in.jsx - // Render the sign in form. - // Collect user's email address and send an email link with which - // they can sign in. - function SignIn() { - const [emailAddress, setEmailAddress] = React.useState(""); - const [expired, setExpired] = React.useState(false); - const [verified, setVerified] = React.useState(false); - const router = useRouter(); - const { signIn, isLoaded, setActive } = useSignIn(); - - if (!isLoaded) { - return null; - } - - const { startEmailLinkFlow, cancelEmailLinkFlow } = signIn.createEmailLinkFlow(); - - async function submit(e) { - e.preventDefault(); - setExpired(false); - setVerified(false); - - // Start the sign in flow, by collecting - // the user's email address. - const si = await signIn.create({ identifier: emailAddress }); - const { emailAddressId } = si.supportedFirstFactors.find( - ff => ff.strategy === "email_link" && ff.safeIdentifier === emailAddress - ); - - // Start the email link flow. - // Pass your app URL that users will be navigated - // res will hold the updated sign in object. - const res = await startEmailLinkFlow({ - emailAddressId: emailAddressId, - redirectUrl: "https://your-app.domain.com/verification", - }); - - // Check the verification result. - const verification = res.firstFactorVerification; - if (verification.verifiedFromTheSameClient()) { - setVerified(true); - // If you're handling the verification result from - // another route/component, you should return here. - // See the component as an - // example below. - // If you want to complete the flow on this tab, - // don't return. Simply check the sign in status. - return; - } else if (verification.status === "expired") { - setExpired(true); - } - if (res.status === "complete") { - setActive({ session: res.createdSessionId }) - //Handle redirect - return; + async function submit(e) { + e.preventDefault(); + const res = await user.createEmailAddress({ email }); + setEmailAddress(res); } - } - if (expired) { - return ( -
Email link has expired
- ); - } - - if (verified) { - return ( -
Signed in on other tab
- ); - } - - return ( -
- setEmailAddress(e.target.value)} - /> - -
- ); - } - - // pages/verification.jsx - // Handle email link verification results. This is - // the final step in the email link flow. - function Verification() { - const [ - verificationStatus, - setVerificationStatus, - ] = React.useState("loading"); - - const { handleEmailLinkVerification } = useClerk(); - - React.useEffect(() => { - async function verify() { - try { - await handleEmailLinkVerification({ - redirectUrl: "https://redirect-to-pending-sign-in-like-2fa", - redirectUrlComplete: "https://redirect-when-sign-in-complete", - }); - // If we're not redirected at this point, it means - // that the flow has completed on another device. - setVerificationStatus("verified"); - } catch (err) { - // Verification has failed. - let status = "failed"; - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { - status = "expired"; - } - setVerificationStatus(status); - } + if (emailAddress && !verified) { + return ( + setVerified(true)} + /> + ); } - verify(); - }, []); - if (verificationStatus === "loading") { - return
Loading...
- } - - if (verificationStatus === "failed") { return ( -
Email link verification failed
+
+ setEmail(e.target.value)} + /> +
); } - if (verificationStatus === "expired") { - return ( -
Email link expired
- ); - } - - return ( -
- Successfully signed in. Return to the original tab to continue. -
- ); - } - ``` - - ```jsx {{ prettier: false }} - import React from "react"; - import { - BrowserRouter as Router, - Routes, - Route, - useNavigate, - } from "react-router-dom"; - import { - ClerkProvider, - ClerkLoaded, - EmailLinkErrorCode, - isEmailLinkError, - UserButton, - useClerk, - useSignIn, - } from "@clerk/clerk-react"; - - const publishableKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY; - - function App() { - return ( - - - - {/* Root path shows sign in page. */} - - - - - - - - - } - /> + // A page which verifies email addresses with email links. + function VerifyWithEmailLink({ + emailAddress, + onVerify, + }) { + const { startEmailLinkFlow } = useEmailLink(emailAddress); - {/* Define a /verification route that handles email link result */} - - - - } /> - - - - ); - } + React.useEffect(() => { + verify(); + }, []); - // Render the sign in form. - // Collect user's email address and send an email link with which - // they can sign in. - function SignInEmailLink() { - const [emailAddress, setEmailAddress] = React.useState(""); - const [expired, setExpired] = React.useState(false); - const [verified, setVerified] = React.useState(false); - const navigate = useNavigate(); - const { signIn, isLoaded, setActive } = useSignIn(); - - if (!isLoaded) { - return null; - } - - const { startEmailLinkFlow, cancelEmailLinkFlow } = signIn.createEmailLinkFlow(); - - async function submit(e) { - e.preventDefault(); - setExpired(false); - setVerified(false); - - // Start the sign in flow, by collecting - // the user's email address. - const si = await signIn.create({ identifier: emailAddress }); - const { emailAddressId } = si.supportedFirstFactors.find( - ff => ff.strategy === "email_link" && ff.safeIdentifier === emailAddress - ); - - // Start the email link flow. - // Pass your app URL that users will be navigated - // res will hold the updated sign in object. - const res = await startEmailLinkFlow({ - emailAddressId: emailAddressId, - redirectUrl: "https://your-app.domain.com/verification", - }); - - // Check the verification result. - const verification = res.firstFactorVerification; - if (verification.verifiedFromTheSameClient()) { - setVerified(true); - // If you're handling the verification result from - // another route/component, you should return here. - // See the component as an - // example below. - // If you want to complete the flow on this tab, - // don't return. Simply check the sign in status. - return; - } else if (verification.status === "expired") { - setExpired(true); - } - if (res.status === "complete") { - // Sign in is complete, we have a session. - // Navigate to the after sign in URL. - setActive({ - session: res.createdSessionId, - beforeEmit: () => navigate("/after-sign-in-path"), + async function verify() { + // Start the email link flow. + // Pass your app URL that users will be navigated + // when they click the email link from their + // email inbox. + const res = await startEmailLinkFlow({ + redirectUrl: "https://redirect-from-email-email-link", }); - return; - } - } - - if (expired) { - return ( -
Email link has expired
- ); - } - - if (verified) { - return ( -
Signed in on other tab
- ); - } - - return ( -
- setEmailAddress(e.target.value)} - /> - -
- ); - } - // Handle email link verification results. This is - // the final step in the email link flow. - function EmailLinkVerification() { - const [ - verificationStatus, - setVerificationStatus, - ] = React.useState("loading"); - - const { handleEmailLinkVerification } = useClerk(); - - React.useEffect(() => { - async function verify() { - try { - await handleEmailLinkVerification({ - redirectUrl: "https://redirect-to-pending-sign-in-like-2fa", - redirectUrlComplete: "https://redirect-when-sign-in-complete", - }); - // If we're not redirected at this point, it means - // that the flow has completed on another device. - setVerificationStatus("verified"); - } catch (err) { - // Verification has failed. - let status = "failed"; - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { - status = "expired"; - } - setVerificationStatus(status); + // res will hold the updated EmailAddress object. + if (res.verification.status === "verified") { + onVerify(); + } else { + // act accordingly } } - verify(); - }, []); - - if (verificationStatus === "loading") { - return
Loading...
- } - - if (verificationStatus === "failed") { - return ( -
Email link verification failed
- ); - } - - if (verificationStatus === "expired") { - return ( -
Email link expired
- ); - } - - return ( -
- Successfully signed in. Return to the original tab to continue. -
- ); - } - - export default App; - ``` - - ```js {{ prettier: false }} - const signIn = window.Clerk.client.signIn; - const { - startEmailLinkFlow, - cancelEmailLinkFlow, - } = signIn.createEmailLinkFlow(); - - const { email_address_id } = signIn.supportedFirstFactors.find( - ff => ff.strategy === "email_link" - && ff.safe_identifier === "your-users-email" - ); - - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - emailAddressId, - redirectUrl: "https://redirect-from-email-email-link", - }); - if (res.status === "completed") { - // sign in completed - } else { - // sign in still pending - } - // Cleanup - cancelEmailLinkFlow(); - ``` -
- -#### Set up email address verification using a custom flow (Clerk API) - -Email links can also provide a nice user experience for verifying email addresses that users add when updating their profiles. The flow is similar to one-time code verification, but users need only click on the email link; there's no need to return to your app. - -1. Collect the user's email address. -1. Start the email link verification flow. There are two parts to the flow: - -- Prepare a verification for the email address by sending an email with an email link to the user. -- Wait until the email link is clicked. This is a polling behavior that can be canceled at any time. - -3. Handle the email link verification result accordingly. Note that even if the email link is clicked on a different device/browser than the one which initiated the flow, the session will be created only on the original device. - -- The verification was successful. -- The verification failed or the email link has expired. - -Clerk provides a highly flexible API that allows you to hook into any of the above steps while abstracting away all the complexities of an email link-based email address verification. - - - ```jsx {{ prettier: false }} - import React from "react"; - import { useUser, useEmailLink } from "@clerk/nextjs"; - - // A page where users can add a new email address. - function NewEmailPage() { - const [email, setEmail] = React.useState(''); - const [emailAddress, setEmailAddress] = React.useState(null); - const [verified, setVerified] = React.useState(false); - - const { user } = useUser(); - - async function submit(e) { - e.preventDefault(); - const res = await user.createEmailAddress({ email }); - setEmailAddress(res); - } - - if (emailAddress && !verified) { - return ( - setVerified(true)} - /> - ); - } - - return ( -
- setEmail(e.target.value)} - /> -
- ); - } - - // A page which verifies email addresses with email links. - function VerifyWithEmailLink({ - emailAddress, - onVerify, - }) { - const { startEmailLinkFlow } = useEmailLink(emailAddress); - - React.useEffect(() => { - verify(); - }, []); - - async function verify() { - // Start the email link flow. - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - redirectUrl: "https://redirect-from-email-email-link", - }); - - // res will hold the updated EmailAddress object. - if (res.verification.status === "verified") { - onVerify(); - } else { - // act accordingly - } - } - - return ( -
- Waiting for verification... -
- ); - } - ``` - - ```jsx {{ prettier: false }} - import React from "react"; - import { useUser, useEmailLink } from "@clerk/clerk-react"; - - // A page where users can add a new email address. - function NewEmailPage() { - const [email, setEmail] = React.useState(''); - const [emailAddress, setEmailAddress] = React.useState(null); - const [verified, setVerified] = React.useState(false); - - const { user } = useUser(); - - async function submit(e) { - e.preventDefault(); - const res = await user.createEmailAddress({ email }); - setEmailAddress(res); - } - if (emailAddress && !verified) { return ( - setVerified(true)} - /> +
+ Waiting for verification... +
); } + ``` - return ( -
- setEmail(e.target.value)} - /> -
- ); - } - - // A page which verifies email addresses with email links. - function VerifyWithEmailLink({ - emailAddress, - onVerify, - }) { - const { startEmailLinkFlow } = useEmailLink(emailAddress); - - React.useEffect(() => { - verify(); - }, []); - - async function verify() { - // Start the email link flow. - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - redirectUrl: "https://redirect-from-email-email-link", - }); - - // res will hold the updated EmailAddress object. - if (res.verification.status === "verified") { - onVerify(); - } else { - // act accordingly - } - } - - return ( -
- Waiting for verification... -
- ); - } - ``` - - ```js {{ prettier: false }} - const user = window.Clerk.user; - const emailAddress = user.emailAddresses[0]; - const { - startEmailLinkFlow, - cancelEmailLinkFlow, - } = emailAddress.createEmailLinkFlow(); + ```js {{ prettier: false }} + const user = window.Clerk.user; + const emailAddress = user.emailAddresses[0]; + const { + startEmailLinkFlow, + cancelEmailLinkFlow, + } = emailAddress.createEmailLinkFlow(); - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - redirectUrl: "https://redirect-from-email-email-link", - }); - if (res.verification.status === "verified") { - // email address was verified - } else { - // email address wasn't verified - } - // Cleanup - cancelEmailLinkFlow(); - ``` -
+ // Pass your app URL that users will be navigated + // when they click the email link from their + // email inbox. + const res = await startEmailLinkFlow({ + redirectUrl: "https://redirect-from-email-email-link", + }); + if (res.verification.status === "verified") { + // email address was verified + } else { + // email address wasn't verified + } + // Cleanup + cancelEmailLinkFlow(); + ``` +
+
From dc00bec2c32355d261bd1d7d32eaeff1017cdefa Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:13:57 -0500 Subject: [PATCH 3/8] Update docs/authentication/configuration/sign-up-sign-in-options.mdx Co-authored-by: victoria --- docs/authentication/configuration/sign-up-sign-in-options.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication/configuration/sign-up-sign-in-options.mdx b/docs/authentication/configuration/sign-up-sign-in-options.mdx index 6b0c76d358..3a623e675d 100644 --- a/docs/authentication/configuration/sign-up-sign-in-options.mdx +++ b/docs/authentication/configuration/sign-up-sign-in-options.mdx @@ -125,7 +125,7 @@ If a country is disabled, then phone numbers starting with the corresponding cou ### Email link -When the **Email verification link** option is selected as an authentication strategy, users will receive an email message with a link that can be visited in order to complete the authentication process. Email links can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to their profile. +When the **Email verification link** option is selected as an authentication strategy, users receive an email message with a link to complete the authentication process. Email links can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to user profiles. As a security measure, email links expire after 10 minutes to prevent the use of potentially compromised stale links. From d1b84200099af1d04d1ae759abe8681d93bd923d Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:16:22 -0500 Subject: [PATCH 4/8] Apply suggestions from code review Co-authored-by: victoria --- .../configuration/sign-up-sign-in-options.mdx | 9 ++++----- docs/custom-flows/email-links.mdx | 19 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/authentication/configuration/sign-up-sign-in-options.mdx b/docs/authentication/configuration/sign-up-sign-in-options.mdx index 3a623e675d..a2fedfa5ea 100644 --- a/docs/authentication/configuration/sign-up-sign-in-options.mdx +++ b/docs/authentication/configuration/sign-up-sign-in-options.mdx @@ -127,17 +127,16 @@ If a country is disabled, then phone numbers starting with the corresponding cou When the **Email verification link** option is selected as an authentication strategy, users receive an email message with a link to complete the authentication process. Email links can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to user profiles. -As a security measure, email links expire after 10 minutes to prevent the use of potentially compromised stale links. +As a security measure, email links expire after 10 minutes prevent the use of compromised or stale links. #### Require the same device and browser -By default, email links can work on any device. There's no constraint on where the link can be opened. For example, a user could try to sign in from their desktop browser, but open the link from their mobile phone. In this scenario, _the user's sign in would be completed on the desktop browser from which it was initiated, not the mobile phone where it was verified_. As a result, the user would be signed in on their desktop, not their phone. +By default, email links can be opened on any device. There's no restriction on where the link can be accessed. For example, a user could try to sign in from their desktop browser but open the link from their mobile phone. In this case, _the user's sign in would be completed on the desktop browser where the process was initiated, not the mobile phone where the link was verified_. As a result, the user would be signed in on their desktop, not their phone. To configure this setting: -1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username). -1. In the navigation sidebar, select **User & Authentication > Email, Phone, and Username**. -1. In the **Authentication strategies** section, next to **Email verification link**, select the settings cog icon. +1. In the Clerk Dashboard, navigate to the [**Email, phone, username**](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) page. +1. In the **Authentication strategies** section, next to **Email verification link**, select the settings icon. 1. Enable or disable the **Require the same device and browser** setting. ## Verification methods diff --git a/docs/custom-flows/email-links.mdx b/docs/custom-flows/email-links.mdx index 9a27262367..827aac39d5 100644 --- a/docs/custom-flows/email-links.mdx +++ b/docs/custom-flows/email-links.mdx @@ -6,17 +6,17 @@ description: Learn how to build a custom flow using Clerk's API to handle email > [!WARNING] > This guide is for users who want to build a _custom_ user interface using the Clerk API. To use a _prebuilt_ UI, you should use Clerk's [Account Portal pages](/docs/customization/account-portal//overview) or [prebuilt components](/docs/components/overview). -[Email links](/docs/authentication/configuration/sign-up-sign-in-options#email-link) can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to their profile. +[Email links](/docs/authentication/configuration/sign-up-sign-in-options#email-link) can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to user profiles. The email link flow works as follows: 1. The user enters their email address and asks for an email link. 1. Clerk sends an email to the user, containing a link to the verification URL. -1. The user visits the email link. This can happen on the same device where they entered their email address, or on a different device. -1. Clerk will verify the user's identity and advance any sign-up or sign-in attempt that might be in progress. +1. The user visits the email link, either on the same device where they entered their email address or on a different device. +1. Clerk verifies the user's identity and advances any sign-up or sign-in attempt that might be in progress. 1. If the verification is successful, the user is authenticated or their email address is verified, depending on the reason for the email link. -This guide will demonstrate how to use Clerk's API to build a custom flow for handling email links. It will cover the following scenarios: +This guide demonstrates how to use Clerk's API to build a custom flow for handling email links. It covers the following scenarios: - [Sign up](#sign-up-flow) - [Sign in](#sign-in-flow) @@ -29,12 +29,11 @@ This guide will demonstrate how to use Clerk's API to build a custom flow for ha To allow your users to sign up or sign in using email links, you first need configure the appropriate settings in the Clerk Dashboard. - 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username). - 1. In the navigation sidebar, select **User & Authentication > Email, Phone, and Username**. + 1. In the Clerk Dashboard, navigate to the [**Email, phone, username**](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) page. 1. In the **Contact information** section, **Email address** should be enabled. - 1. In the **Username** section, ensure that **Username** is not required, or else the `create()` method will require a username to be passed in the params. If you would like to use usernames, you must handle collecting the username in your custom flow. + 1. In the **Username** section, ensure that **Username** is not required. Otherwise, the `create()` method will require a username to be passed in the params. If you want to use usernames, you must handle collect the username in your custom flow. 1. In the **Authentication strategies** section, toggle on **Email verification link**. - 1. Keep this page open as you will need to enable email link verification in the next step. + 1. Keep this page open to enable email link verification in the next step. ### Enable email link verification @@ -43,12 +42,12 @@ This guide will demonstrate how to use Clerk's API to build a custom flow for ha To allow your users to verify their email addresses using email links, configure the following settings: 1. On this same page in the Clerk Dashboard, next to **Email address**, select the settings cog icon. A modal will open. - 1. Under **Verification methods**, enable the **Email verification link** option. For the sake of this guide, uncheck the box for **Email verification code**. + 1. Under **Verification methods**, enable the **Email verification link** option. For this guide, uncheck the box for **Email verification code**. 1. Select **Continue** to save your changes. ### Create the flows - The flow for handling email links is the same across all scenarios. You must: + The flow for handling email links is the same across all use cases. You must: 1. Initiate the sign-up, sign-in, or email verification process by collecting the user's email address. 1. Prepare the email address verification, which sends an email link to the given address. From bd2cbf08ebaa1a4450d92fbd7f1861e014de4711 Mon Sep 17 00:00:00 2001 From: Roy Anger Date: Tue, 3 Dec 2024 15:02:36 -0500 Subject: [PATCH 5/8] feat: Updated email link code for Core 2 --- docs/custom-flows/email-links.mdx | 268 +++++++++++++++++++++++------- 1 file changed, 207 insertions(+), 61 deletions(-) diff --git a/docs/custom-flows/email-links.mdx b/docs/custom-flows/email-links.mdx index 827aac39d5..0671adc99b 100644 --- a/docs/custom-flows/email-links.mdx +++ b/docs/custom-flows/email-links.mdx @@ -69,78 +69,224 @@ This guide demonstrates how to use Clerk's API to build a custom flow for handli ```jsx {{ prettier: false }} - import React from "react"; - import { useUser, useEmailLink } from "@clerk/nextjs"; - - // A page where users can add a new email address. - function NewEmailPage() { - const [email, setEmail] = React.useState(''); - const [emailAddress, setEmailAddress] = React.useState(null); - const [verified, setVerified] = React.useState(false); - - const { user } = useUser(); +// /sign-in/page.tsx + +'use client'; + +import * as React from 'react'; +import { useRouter } from 'next/navigation'; +import { + useSignIn, +} from '@clerk/nextjs'; +import { EmailLinkFactor, SignInFirstFactor } from '@clerk/types'; + +export default function SignInPage() { + const [emailAddress, setEmailAddress] = React.useState(''); + const [verified, setVerified] = React.useState(false); + const [verifying, setVerifying] = React.useState(false); + const { signIn, isLoaded } = useSignIn(); + + if (!isLoaded) { + return null; + } + + const { startEmailLinkFlow } = + signIn.createEmailLinkFlow(); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + // reset verified and expired state in case user resubmits form mid sign-in + setVerified(false); + setVerifying(true) + + + if (!isLoaded && !signIn) return null; + + try { + // when the user submits the form with their email, start the in flow by creating a singIn object + const { supportedFirstFactors } = await signIn.create({ + identifier: emailAddress, + }); + + // Filter the returned array to find the 'phone_code' entry + const isEmailCodeFactor = ( + factor: SignInFirstFactor, + ): factor is EmailLinkFactor => { + return factor.strategy === 'email_link'; + }; + const emailLinkFactor = supportedFirstFactors?.find(isEmailCodeFactor); + + // @ts-ignore + // TODO: Fix TS error + const { emailAddressId } = emailLinkFactor; + + // You can use window.location.host to dynamically set the host domain for dev and prod + // You could optionally use an environment variable or other source for the host domain + const protocol = window.location.protocol + const host = window.location.host + + // Attempt the first factor verification by sending an email with an email link to the user + // TODO: Alexis -- the path should just be /sign-in for this component and /sign-in/verify for the url two lines down + const res = await startEmailLinkFlow({ + emailAddressId, + redirectUrl: `${protocol}//${host}/custom-flows/sign-in/magic-link/verify`, + }); + + // Check the verification result. + const verification = res.firstFactorVerification; + + // if the user has clicked on the link and completed sign in from /sign-in/verify this will return true + if (verification.verifiedFromTheSameClient()) { + // set 'verified' true to display a success message or redirect the user + setVerifying(false) + setVerified(true); + return; - async function submit(e) { - e.preventDefault(); - const res = await user.createEmailAddress({ email }); - setEmailAddress(res); } + } catch (err: any) { + // TODO: Alexis -- swap out this for the standard 'error handling' from docs + console.error('error', err.errors[0].longMessage); + } + } - if (emailAddress && !verified) { - return ( - setVerified(true)} - /> - ); - } + async function reset(e: React.FormEvent) { + e.preventDefault(); + setVerifying(false) + } - return ( -
- setEmail(e.target.value)} - /> + + if (verifying) { + return ( +
+

Please check your email and click on the link that was sent to you

+ + - ); +
+ ) + } + + if (verified) { + // Repalce this with your own success message or a redirect to another page as desired + return
Signed in on other tab
; + } + + return ( +
+
+ setEmailAddress(e.target.value)} + /> + +
+
+ ); +} + +// /sign-in/verify/page.tsx +'use client' + +import * as React from 'react' +import { useClerk } from "@clerk/nextjs"; +import { EmailLinkErrorCode, isEmailLinkError } from "@clerk/nextjs/errors" +import Link from 'next/link'; +// This can be used once @clerk/types is updated +//import { VerificationStatus } from '@clerk/types'; + +export type VerificationStatus = + | 'expired' + | 'failed' + | 'loading' + | 'verified' + | 'verified_switch_tab' + | 'client_mismatch'; + +// verify the link when the user clicks on the link in their email +export default function VerifyEmailLink() { + const [verificationStatus, setVerificationStatus] = React.useState('loading'); + + const { handleEmailLinkVerification } = useClerk() + + async function verify() { + try { + // You can use window.location.host to dynamically set the host domain for dev and prod + // You could optionally use an environment variable or other source for the host domain + const protocol = window.location.protocol + const host = window.location.host + + await handleEmailLinkVerification({ + // URL to navigate to if the sign-in flow needs more requirements (name, username for sign-up) or MFA/second-factor (for sign-in) + redirectUrl: `${protocol}//${host}/sign-in`, + // What do to when successful + redirectUrlComplete: `${protocol}//${host}/sign-in/verify` + }); + // If we're not redirected at this point, it means + // that the flow has completed on another device. + setVerificationStatus('verified'); + } catch (err: any) { + // Verification has failed. + let status: VerificationStatus = 'failed'; + if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { + status = 'expired'; + } + // This is only required if you have enabled 'Require the same device and browser' in Dashboard -> Auth -> Email, phone, username -> Email verification link settings -> 'Require the same device and brwoser' + if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.ClientMismatch) { + status = 'client_mismatch'; + } + setVerificationStatus(status); } + } - // A page which verifies email addresses with email links. - function VerifyWithEmailLink({ - emailAddress, - onVerify, - }) { - const { startEmailLinkFlow } = useEmailLink(emailAddress); + React.useEffect(() => { + verify(); + }, [handleEmailLinkVerification]); - React.useEffect(() => { - verify(); - }, []); + if (verificationStatus === 'loading') { + return
Loading...
; + } - async function verify() { - // Start the email link flow. - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - redirectUrl: "https://redirect-from-email-email-link", - }); + if (verificationStatus === 'failed') { + return ( +
+

Magic link verification failed

+ Sign In - // res will hold the updated EmailAddress object. - if (res.verification.status === "verified") { - onVerify(); - } else { - // act accordingly - } - } +
+ ) + } - return ( -
- Waiting for verification... -
- ); - } - ``` + if (verificationStatus === 'expired') { + return ( + +
+

Magic link expired

+ Sign In +
+ ) + } + + // TODO: Alexis -- you may not to mention this is also optional, as per above comment + if (verificationStatus === 'client_mismatch') { + return ( + +
+

You must complete the Email Link sign-in on the same device and browser as you started it on

+ Sign In +
+ ) + } + + return ( +
Successfully signed in. Return to the original tab to continue.
+ ); +} + + + + + ``` ```jsx {{ prettier: false }} import React from "react"; From 58b4f1d73fe89c6225f1d0f38ce5363192f14027 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:02:50 -0500 Subject: [PATCH 6/8] update email verification reference doc --- .../javascript/sign-up/email-verification.mdx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/references/javascript/sign-up/email-verification.mdx b/docs/references/javascript/sign-up/email-verification.mdx index 01b5d06fd3..3bb49485b2 100644 --- a/docs/references/javascript/sign-up/email-verification.mdx +++ b/docs/references/javascript/sign-up/email-verification.mdx @@ -7,7 +7,7 @@ These are all methods on the [`SignUp`](/docs/references/javascript/sign-up/sign ## `prepareEmailAddressVerification()` -Helper method that allows you to initiate a verification process for an email address. It basically sends a one-time code to the email address already supplied to the current sign-up. +Initiates an email verification process for the email address associated with the current sign-up attempt. This method sends a verification code or link (depending on the strategy) to the provided email address. The verification method must be [configured in your Clerk Dashboard](/docs/authentication/configuration/sign-up-sign-in-options#verification-methods). The defaults of this method are equivalent to calling [`SignUp.prepareVerification('email_code')`](/docs/references/javascript/sign-up/verification#prepareverification). @@ -23,19 +23,19 @@ function prepareEmailAddressVerification( - `strategy` - `'email_code' | 'email_link'` - The verification strategy to validate the user's sign-up request.
The following strategies are supported:
  • `email_code`: Send an email with a unique token to input.
  • `email_link`: Send an email with a link which validates sign-up
+ The strategy used to verify the user's email address during sign-up. Supported values:
  • `email_code`: Sends a one-time verification code via email that the user must input to verify their address
  • `email_link`: Sends an email containing a verification link that, when visited, automatically verifies the user's email address
--- - `redirectUrl` - `string` - The URL to redirect the user to after the verification process is complete.
Only supported on `email_link`, `oauth_`, and `saml` strategies. + The URL to redirect the user to after the verification process is complete.
Only supported with `email_link`, `oauth_`, and `saml` strategies. ## `attemptEmailAddressVerification()` -Helper method that attempts to complete the verification process for an email address. It basically verifies that the supplied code is the same as the one-time code that was sent to the email address during the prepare verification phase. +Validates an email verification code during sign-up. **This method is only required when using the `email_code` verification strategy.** It compares the provided verification code against the one-time code sent to the user's email address during the `prepareEmailAddressVerification()` step. If the codes match, the email address is marked as verified. This is equivalent to calling [`SignUp.attemptVerification({strategy: 'email_code', ...params})`](/docs/references/javascript/sign-up/verification#attempt-verification). @@ -53,5 +53,3 @@ function attemptEmailAddressVerification( The code that was sent to the user via email. - -[signup-ref]: /docs/references/javascript/sign-up/sign-up From e2b82f10dfb1333c90d2d13c09cdbbee3ab4d508 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:28:17 -0500 Subject: [PATCH 7/8] add partial; fix format of doc --- docs/custom-flows/email-links.mdx | 556 ++++++++++++------------------ 1 file changed, 230 insertions(+), 326 deletions(-) diff --git a/docs/custom-flows/email-links.mdx b/docs/custom-flows/email-links.mdx index 0671adc99b..e9839e55e6 100644 --- a/docs/custom-flows/email-links.mdx +++ b/docs/custom-flows/email-links.mdx @@ -3,8 +3,7 @@ title: Build a custom flow for handling email links description: Learn how to build a custom flow using Clerk's API to handle email links for sign-up, sign-in, and email address verification. --- -> [!WARNING] -> This guide is for users who want to build a _custom_ user interface using the Clerk API. To use a _prebuilt_ UI, you should use Clerk's [Account Portal pages](/docs/customization/account-portal//overview) or [prebuilt components](/docs/components/overview). + [Email links](/docs/authentication/configuration/sign-up-sign-in-options#email-link) can be used to sign up new users, sign in existing ones, or allow existing users to verify newly entered email addresses to user profiles. @@ -27,22 +26,22 @@ This guide demonstrates how to use Clerk's API to build a custom flow for handli ### Enable email link authentication - To allow your users to sign up or sign in using email links, you first need configure the appropriate settings in the Clerk Dashboard. + To allow your users to sign up or sign in using email links, you must first configure the appropriate settings in the Clerk Dashboard. 1. In the Clerk Dashboard, navigate to the [**Email, phone, username**](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) page. - 1. In the **Contact information** section, **Email address** should be enabled. - 1. In the **Username** section, ensure that **Username** is not required. Otherwise, the `create()` method will require a username to be passed in the params. If you want to use usernames, you must handle collect the username in your custom flow. + 1. In the **Contact information** section, **Email address** should be enabled and required. + 1. In the **Username** section, ensure that **Username** is not required. If you want to require a username, you must collect it and pass it to the `create()` method (you'll see how it's used later in the code examples). 1. In the **Authentication strategies** section, toggle on **Email verification link**. 1. Keep this page open to enable email link verification in the next step. ### Enable email link verification - [**Verification methods**](/docs/authentication/configuration/sign-up-sign-in-options#verification-methods) are different from [**Authentication strategies**](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategies). **Authentication strategies** are used for authenticating a user, such as when they are signing in to your application. **Verification methods** are used for verifying a user's identifier, such as an email address upon initial sign-up or when updating their profile. + [**Verification methods**](/docs/authentication/configuration/sign-up-sign-in-options#verification-methods) are different from [**authentication strategies**](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategies). **Authentication strategies** are used for authenticating a user, such as when they are signing in to your application. **Verification methods** are used for verifying a user's identifier, such as an email address upon initial sign-up or when updating their profile. To allow your users to verify their email addresses using email links, configure the following settings: - 1. On this same page in the Clerk Dashboard, next to **Email address**, select the settings cog icon. A modal will open. - 1. Under **Verification methods**, enable the **Email verification link** option. For this guide, uncheck the box for **Email verification code**. + 1. On the [**Email, phone, username**](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) page of the Clerk Dashboard, next to **Email address**, select the settings icon. A modal will open. + 1. Under **Verification methods**, enable the **Email verification link** option. You can leave **Require the same device and browser** enabled, or disable it if you want to allow users to verify their email addresses on different devices. The code examples will show how to handle either option. Because this guide focuses on email links, uncheck the box for **Email verification code**. 1. Select **Continue** to save your changes. ### Create the flows @@ -59,330 +58,235 @@ This guide demonstrates how to use Clerk's API to build a custom flow for handli In the following example, the user is prompted to enter their email address and then click a button to sign up using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. + + + ```tsx + ``` + + + ### Sign-in flow In the following example, the user is prompted to enter their email address and then click a button to sign in using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. + + + + ```tsx {{ filename: '/sign-in/page.tsx' }} + 'use client' + + import * as React from 'react' + import { useSignIn } from '@clerk/nextjs' + import { EmailLinkFactor, SignInFirstFactor } from '@clerk/types' + + export default function SignInPage() { + const [emailAddress, setEmailAddress] = React.useState('') + const [verified, setVerified] = React.useState(false) + const [verifying, setVerifying] = React.useState(false) + const { signIn, isLoaded } = useSignIn() + + if (!isLoaded) { + return null + } + + const { startEmailLinkFlow } = signIn.createEmailLinkFlow() + + async function submit(e: React.FormEvent) { + e.preventDefault() + // reset verified and expired state in case user resubmits form mid sign-in + setVerified(false) + setVerifying(true) + + if (!isLoaded && !signIn) return null + + try { + // when the user submits the form with their email, start the in flow by creating a singIn object + const { supportedFirstFactors } = await signIn.create({ + identifier: emailAddress, + }) + + // Filter the returned array to find the 'phone_code' entry + const isEmailCodeFactor = (factor: SignInFirstFactor): factor is EmailLinkFactor => { + return factor.strategy === 'email_link' + } + const emailLinkFactor = supportedFirstFactors?.find(isEmailCodeFactor) + + // @ts-ignore + // TODO: Fix TS error + const { emailAddressId } = emailLinkFactor + + // You can use window.location.host to dynamically set the host domain for dev and prod + // You could optionally use an environment variable or other source for the host domain + const protocol = window.location.protocol + const host = window.location.host + + // Attempt the first factor verification by sending an email with an email link to the user + // TODO: Alexis -- the path should just be /sign-in for this component and /sign-in/verify for the url two lines down + const res = await startEmailLinkFlow({ + emailAddressId, + redirectUrl: `${protocol}//${host}/custom-flows/sign-in/magic-link/verify`, + }) + + // Check the verification result. + const verification = res.firstFactorVerification + + // if the user has clicked on the link and completed sign in from /sign-in/verify this will return true + if (verification.verifiedFromTheSameClient()) { + // set 'verified' true to display a success message or redirect the user + setVerifying(false) + setVerified(true) + return + } + } catch (err: any) { + // TODO: Alexis -- swap out this for the standard 'error handling' from docs + console.error('error', err.errors[0].longMessage) + } + } + + async function reset(e: React.FormEvent) { + e.preventDefault() + setVerifying(false) + } + + if (verifying) { + return ( +
+

Please check your email and click on the link that was sent to you

+
+ +
+
+ ) + } + + if (verified) { + // Repalce this with your own success message or a redirect to another page as desired + return
Signed in on other tab
+ } + + return ( +
+
+ setEmailAddress(e.target.value)} + /> + +
+
+ ) + } + ``` + + ```tsx {{ filename: '/sign-in/verify/page.tsx' }} + 'use client' + + import * as React from 'react' + import { useClerk } from '@clerk/nextjs' + import { EmailLinkErrorCode, isEmailLinkError } from '@clerk/nextjs/errors' + import Link from 'next/link' + // This can be used once @clerk/types is updated + //import { VerificationStatus } from '@clerk/types'; + + export type VerificationStatus = + | 'expired' + | 'failed' + | 'loading' + | 'verified' + | 'verified_switch_tab' + | 'client_mismatch' + + // verify the link when the user clicks on the link in their email + export default function VerifyEmailLink() { + const [verificationStatus, setVerificationStatus] = React.useState('loading') + + const { handleEmailLinkVerification } = useClerk() + + async function verify() { + try { + // You can use window.location.host to dynamically set the host domain for dev and prod + // You could optionally use an environment variable or other source for the host domain + const protocol = window.location.protocol + const host = window.location.host + + await handleEmailLinkVerification({ + // URL to navigate to if the sign-in flow needs more requirements (name, username for sign-up) or MFA/second-factor (for sign-in) + redirectUrl: `${protocol}//${host}/sign-in`, + // What do to when successful + redirectUrlComplete: `${protocol}//${host}/sign-in/verify`, + }) + // If we're not redirected at this point, it means + // that the flow has completed on another device. + setVerificationStatus('verified') + } catch (err: any) { + // Verification has failed. + let status: VerificationStatus = 'failed' + if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { + status = 'expired' + } + // This is only required if you have enabled 'Require the same device and browser' in Dashboard -> Auth -> Email, phone, username -> Email verification link settings -> 'Require the same device and brwoser' + if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.ClientMismatch) { + status = 'client_mismatch' + } + setVerificationStatus(status) + } + } + + React.useEffect(() => { + verify() + }, [handleEmailLinkVerification]) + + if (verificationStatus === 'loading') { + return
Loading...
+ } + + if (verificationStatus === 'failed') { + return ( +
+

Magic link verification failed

+ Sign In +
+ ) + } + + if (verificationStatus === 'expired') { + return ( +
+

Magic link expired

+ Sign In +
+ ) + } + + // TODO: Alexis -- you may not to mention this is also optional, as per above comment + if (verificationStatus === 'client_mismatch') { + return ( +
+

+ You must complete the Email Link sign-in on the same device and browser as you started it + on +

+ Sign In +
+ ) + } + + return
Successfully signed in. Return to the original tab to continue.
+ } + ``` +
+
+
+ ### Email address verification flow In the following example, the user is prompted to enter their email address and then click a button to verify their email address using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. - - ```jsx {{ prettier: false }} -// /sign-in/page.tsx - -'use client'; - -import * as React from 'react'; -import { useRouter } from 'next/navigation'; -import { - useSignIn, -} from '@clerk/nextjs'; -import { EmailLinkFactor, SignInFirstFactor } from '@clerk/types'; - -export default function SignInPage() { - const [emailAddress, setEmailAddress] = React.useState(''); - const [verified, setVerified] = React.useState(false); - const [verifying, setVerifying] = React.useState(false); - const { signIn, isLoaded } = useSignIn(); - - if (!isLoaded) { - return null; - } - - const { startEmailLinkFlow } = - signIn.createEmailLinkFlow(); - - async function submit(e: React.FormEvent) { - e.preventDefault(); - // reset verified and expired state in case user resubmits form mid sign-in - setVerified(false); - setVerifying(true) - - - if (!isLoaded && !signIn) return null; - - try { - // when the user submits the form with their email, start the in flow by creating a singIn object - const { supportedFirstFactors } = await signIn.create({ - identifier: emailAddress, - }); - - // Filter the returned array to find the 'phone_code' entry - const isEmailCodeFactor = ( - factor: SignInFirstFactor, - ): factor is EmailLinkFactor => { - return factor.strategy === 'email_link'; - }; - const emailLinkFactor = supportedFirstFactors?.find(isEmailCodeFactor); - - // @ts-ignore - // TODO: Fix TS error - const { emailAddressId } = emailLinkFactor; - - // You can use window.location.host to dynamically set the host domain for dev and prod - // You could optionally use an environment variable or other source for the host domain - const protocol = window.location.protocol - const host = window.location.host - - // Attempt the first factor verification by sending an email with an email link to the user - // TODO: Alexis -- the path should just be /sign-in for this component and /sign-in/verify for the url two lines down - const res = await startEmailLinkFlow({ - emailAddressId, - redirectUrl: `${protocol}//${host}/custom-flows/sign-in/magic-link/verify`, - }); - - // Check the verification result. - const verification = res.firstFactorVerification; - - // if the user has clicked on the link and completed sign in from /sign-in/verify this will return true - if (verification.verifiedFromTheSameClient()) { - // set 'verified' true to display a success message or redirect the user - setVerifying(false) - setVerified(true); - return; - - } - } catch (err: any) { - // TODO: Alexis -- swap out this for the standard 'error handling' from docs - console.error('error', err.errors[0].longMessage); - } - } - - async function reset(e: React.FormEvent) { - e.preventDefault(); - setVerifying(false) - } - - - if (verifying) { - return ( -
-

Please check your email and click on the link that was sent to you

-
- -
-
- ) - } - - if (verified) { - // Repalce this with your own success message or a redirect to another page as desired - return
Signed in on other tab
; - } - - return ( -
-
- setEmailAddress(e.target.value)} - /> - -
-
- ); -} - -// /sign-in/verify/page.tsx -'use client' - -import * as React from 'react' -import { useClerk } from "@clerk/nextjs"; -import { EmailLinkErrorCode, isEmailLinkError } from "@clerk/nextjs/errors" -import Link from 'next/link'; -// This can be used once @clerk/types is updated -//import { VerificationStatus } from '@clerk/types'; - -export type VerificationStatus = - | 'expired' - | 'failed' - | 'loading' - | 'verified' - | 'verified_switch_tab' - | 'client_mismatch'; - -// verify the link when the user clicks on the link in their email -export default function VerifyEmailLink() { - const [verificationStatus, setVerificationStatus] = React.useState('loading'); - - const { handleEmailLinkVerification } = useClerk() - - async function verify() { - try { - // You can use window.location.host to dynamically set the host domain for dev and prod - // You could optionally use an environment variable or other source for the host domain - const protocol = window.location.protocol - const host = window.location.host - - await handleEmailLinkVerification({ - // URL to navigate to if the sign-in flow needs more requirements (name, username for sign-up) or MFA/second-factor (for sign-in) - redirectUrl: `${protocol}//${host}/sign-in`, - // What do to when successful - redirectUrlComplete: `${protocol}//${host}/sign-in/verify` - }); - // If we're not redirected at this point, it means - // that the flow has completed on another device. - setVerificationStatus('verified'); - } catch (err: any) { - // Verification has failed. - let status: VerificationStatus = 'failed'; - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { - status = 'expired'; - } - // This is only required if you have enabled 'Require the same device and browser' in Dashboard -> Auth -> Email, phone, username -> Email verification link settings -> 'Require the same device and brwoser' - if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.ClientMismatch) { - status = 'client_mismatch'; - } - setVerificationStatus(status); - } - } - - React.useEffect(() => { - verify(); - }, [handleEmailLinkVerification]); - - if (verificationStatus === 'loading') { - return
Loading...
; - } - - if (verificationStatus === 'failed') { - return ( -
-

Magic link verification failed

- Sign In - -
- ) - } - - if (verificationStatus === 'expired') { - return ( - -
-

Magic link expired

- Sign In -
- ) - } - - // TODO: Alexis -- you may not to mention this is also optional, as per above comment - if (verificationStatus === 'client_mismatch') { - return ( - -
-

You must complete the Email Link sign-in on the same device and browser as you started it on

- Sign In -
- ) - } - - return ( -
Successfully signed in. Return to the original tab to continue.
- ); -} - - - - - ``` - - ```jsx {{ prettier: false }} - import React from "react"; - import { useUser, useEmailLink } from "@clerk/clerk-react"; - - // A page where users can add a new email address. - function NewEmailPage() { - const [email, setEmail] = React.useState(''); - const [emailAddress, setEmailAddress] = React.useState(null); - const [verified, setVerified] = React.useState(false); - - const { user } = useUser(); - - async function submit(e) { - e.preventDefault(); - const res = await user.createEmailAddress({ email }); - setEmailAddress(res); - } - - if (emailAddress && !verified) { - return ( - setVerified(true)} - /> - ); - } - - return ( -
- setEmail(e.target.value)} - /> -
- ); - } - - // A page which verifies email addresses with email links. - function VerifyWithEmailLink({ - emailAddress, - onVerify, - }) { - const { startEmailLinkFlow } = useEmailLink(emailAddress); - - React.useEffect(() => { - verify(); - }, []); - - async function verify() { - // Start the email link flow. - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - redirectUrl: "https://redirect-from-email-email-link", - }); - - // res will hold the updated EmailAddress object. - if (res.verification.status === "verified") { - onVerify(); - } else { - // act accordingly - } - } - - return ( -
- Waiting for verification... -
- ); - } - ``` - - ```js {{ prettier: false }} - const user = window.Clerk.user; - const emailAddress = user.emailAddresses[0]; - const { - startEmailLinkFlow, - cancelEmailLinkFlow, - } = emailAddress.createEmailLinkFlow(); - - // Pass your app URL that users will be navigated - // when they click the email link from their - // email inbox. - const res = await startEmailLinkFlow({ - redirectUrl: "https://redirect-from-email-email-link", - }); - if (res.verification.status === "verified") { - // email address was verified - } else { - // email address wasn't verified - } - // Cleanup - cancelEmailLinkFlow(); - ``` -
+ + + ```tsx + ``` + +
From 486248373f54527db1d20f0164d5488ba1b9e7af Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:31:05 -0500 Subject: [PATCH 8/8] add broken sign-up code --- docs/custom-flows/email-links.mdx | 220 ++++++++++++++++++++++++++++-- 1 file changed, 211 insertions(+), 9 deletions(-) diff --git a/docs/custom-flows/email-links.mdx b/docs/custom-flows/email-links.mdx index e9839e55e6..c21d1692ac 100644 --- a/docs/custom-flows/email-links.mdx +++ b/docs/custom-flows/email-links.mdx @@ -31,7 +31,7 @@ This guide demonstrates how to use Clerk's API to build a custom flow for handli 1. In the Clerk Dashboard, navigate to the [**Email, phone, username**](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) page. 1. In the **Contact information** section, **Email address** should be enabled and required. 1. In the **Username** section, ensure that **Username** is not required. If you want to require a username, you must collect it and pass it to the `create()` method (you'll see how it's used later in the code examples). - 1. In the **Authentication strategies** section, toggle on **Email verification link**. + 1. In the **Authentication strategies** section, ensure **only** **Email verification link** is enabled. 1. Keep this page open to enable email link verification in the next step. ### Enable email link verification @@ -60,8 +60,210 @@ This guide demonstrates how to use Clerk's API to build a custom flow for handli - ```tsx - ``` + + ```tsx {{ filename: '/sign-up/page.tsx' }} + 'use client' + + import * as React from 'react' + import { useSignUp } from '@clerk/nextjs' + + export default function SignInPage() { + const [emailAddress, setEmailAddress] = React.useState('') + const [verified, setVerified] = React.useState(false) + const [verifying, setVerifying] = React.useState(false) + const { signUp, isLoaded } = useSignUp() + + if (!isLoaded) { + return null + } + + const { startEmailLinkFlow } = signUp.createEmailLinkFlow() + + async function submit(e: React.FormEvent) { + e.preventDefault() + // Reset states in case user resubmits form mid sign-up + setVerified(false) + setVerifying(true) + + if (!isLoaded && !signUp) return null + + // Start the sign-up process using the email provided + try { + await signUp.create({ + emailAddress, + }) + + // Dynamically set the host domain for dev and prod + // You could instead use an environment variable or other source for the host domain + const protocol = window.location.protocol + const host = window.location.host + + // Send the user an email with the email link + const signUpAttempt = await startEmailLinkFlow({ + // URL to navigate to after the user visits the link in their email + redirectUrl: `${protocol}//${host}/sign-up/verify`, + }) + + // Check the verification result + const verification = signUpAttempt.verifications.emailAddress + + // Handle if user visited the link and completed sign-up from /sign-up/verify + if (verification.verifiedFromTheSameClient()) { + setVerifying(false) + setVerified(true) + return + } + } catch (err: any) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error(JSON.stringify(err, null, 2)) + } + } + + async function reset(e: React.FormEvent) { + e.preventDefault() + setVerifying(false) + } + + if (verifying) { + return ( +
+

Check your email and visit the link that was sent to you.

+
+ +
+
+ ) + } + + if (verified) { + return
Signed up successfully!
+ } + + return ( +
+

Sign up

+
+ setEmailAddress(e.target.value)} + /> + +
+
+ ) + } + ``` + + ```tsx {{ filename: '/sign-up/verify/page.tsx' }} + 'use client' + + import * as React from 'react' + import { useClerk } from '@clerk/nextjs' + import { EmailLinkErrorCode, isEmailLinkError } from '@clerk/nextjs/errors' + import Link from 'next/link' + // TODO: Use once @clerk/types is updated + //import { VerificationStatus } from '@clerk/types'; + + // TODO: Remove once @clerk/types is updated + export type VerificationStatus = + | 'expired' + | 'failed' + | 'loading' + | 'verified' + | 'verified_switch_tab' + | 'client_mismatch' + + export default function VerifyEmailLink() { + const [verificationStatus, setVerificationStatus] = React.useState('loading') + + const { handleEmailLinkVerification } = useClerk() + + async function verify() { + try { + // Dynamically set the host domain for dev and prod + // You could instead use an environment variable or other source for the host domain + const protocol = window.location.protocol + const host = window.location.host + + await handleEmailLinkVerification({ + // URL to navigate to if sign-in flow needs more requirements + redirectUrl: `${protocol}//${host}/sign-in`, + // URL to navigate to if sign-in flow is complete + redirectUrlComplete: `${protocol}//${host}/sign-in/verify`, + }) + // If not redirected at this point, + // the flow has completed (email was verified) on another device. + setVerificationStatus('verified') + } catch (err: any) { + // Set status to failed + let status: VerificationStatus = 'failed' + + // If link expired, set status to expired + if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.Expired) { + status = 'expired' + } + + // OPTIONAL: This check is only required if you have + // the 'Require the same device and browser' setting + // enabled in the Clerk Dashboard + if (isEmailLinkError(err) && err.code === EmailLinkErrorCode.ClientMismatch) { + status = 'client_mismatch' + } + + setVerificationStatus(status) + } + } + + React.useEffect(() => { + verify() + }, [handleEmailLinkVerification]) + + if (verificationStatus === 'loading') { + return
Loading...
+ } + + if (verificationStatus === 'failed') { + return ( +
+

Verify your email

+

The email link verification failed.

+ Sign up +
+ ) + } + + if (verificationStatus === 'expired') { + return ( +
+

Verify your email

+

The email link has expired.

+ Sign up +
+ ) + } + + // OPTIONAL: This check is only required if you have + // the 'Require the same device and browser' setting + // enabled in the Clerk Dashboard + if (verificationStatus === 'client_mismatch') { + return ( +
+

Verify your email

+

+ You must complete the email link sign-up on the same device and browser that you started + it on. +

+ Sign up +
+ ) + } + + return
Successfully signed up. Return to the original tab to continue.
+ } + ``` +
@@ -283,10 +485,10 @@ This guide demonstrates how to use Clerk's API to build a custom flow for handli In the following example, the user is prompted to enter their email address and then click a button to verify their email address using an email link. The user is then redirected to a verification page where they can see the result of the email link verification. - - - ```tsx - ``` - - + {/* + + ```tsx + ``` + + */}