From 1955a9e3a483bb1b8e0e36750c665b4f598741d7 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:06:27 -0400 Subject: [PATCH] Update invitations (user and organizations); update custom flow for using tokens; add guide on accepting organization invitations (#1509) Co-authored-by: panteliselef Co-authored-by: victoria --- docs/custom-flows/embedded-email-links.mdx | 119 +++++++----- docs/custom-flows/invitations.mdx | 93 +++++---- docs/manifest.json | 6 +- .../accept-organization-invitations.mdx | 181 ++++++++++++++++++ docs/organizations/invitations.mdx | 135 +++++++++++++ docs/organizations/inviting-users.mdx | 37 ++-- docs/users/invitations.mdx | 38 ++-- 7 files changed, 479 insertions(+), 130 deletions(-) create mode 100644 docs/organizations/accept-organization-invitations.mdx create mode 100644 docs/organizations/invitations.mdx diff --git a/docs/custom-flows/embedded-email-links.mdx b/docs/custom-flows/embedded-email-links.mdx index 78346f71ae..f77c704585 100644 --- a/docs/custom-flows/embedded-email-links.mdx +++ b/docs/custom-flows/embedded-email-links.mdx @@ -3,7 +3,7 @@ title: Embeddable email links with sign-in tokens description: Learn how to build custom embeddable email link sign-in flows to increase user engagement and reduce drop off in transactional emails, SMS's, and more. --- -An "email link" is a link that, when visited, will automatically authenticate your user so that they can perform some action on your site with less friction than if they had to sign in manually. You can create email links with Clerk by generating a sign-in token, which you can detect and take action on in your frontend. +An "email link" is a link that, when visited, will automatically authenticate your user so that they can perform some action on your site with less friction than if they had to sign in manually. You can create email links with Clerk by generating a sign-in token. Common use cases include: @@ -12,17 +12,17 @@ Common use cases include: - Recovering abandoned carts - Surveys or questionnaires -This guide will teach you how to set this flow up. +This guide will demonstrate how to generate a sign-in token and use it to sign in a user. ### Generate a sign-in token - Embeddable email links leverage [sign-in tokens](/docs/reference/backend-api/tag/Sign-in-Tokens#operation/CreateSignInToken){{ target: '_blank' }}, which are JWTs that can be used to sign in to an application without specifying any credentials. A sign-in token can be used at most once, and can be consumed from the Frontend API using the [`ticket`](/docs/references/javascript/sign-in/sign-in#sign-in-create-params) strategy. + [Sign-in tokens](/docs/reference/backend-api/tag/Sign-in-Tokens#operation/CreateSignInToken){{ target: '_blank' }} are JWTs that can be used to sign in to an application without specifying any credentials. A sign-in token can be used **once**, and can be consumed from the Frontend API using the [`ticket`](/docs/references/javascript/sign-in/sign-in#sign-in-create-params) strategy, which is demonstrated in the following example. > [!NOTE] - > By default, sign-in tokens expire in 30 days. You can optionally supply a different duration in seconds using the `expires_in_seconds` property. + > By default, sign-in tokens expire in 30 days. You can optionally specify a different duration in seconds using the `expires_in_seconds` property. - The following example demonstrates a cURL request that fetches a valid sign-in token: + The following example demonstrates a cURL request that creates a valid sign-in token: ```bash curl 'https://api.clerk.com/v1/sign_in_tokens' \ @@ -32,17 +32,19 @@ This guide will teach you how to set this flow up. -d '{ "user_id": "user_123" }' ``` - This will return a token, which can then be embedded as a query param in any link, such as the following example: + This will return a `url` property, which can be used as your email link. Keep in mind that this link will use Clerk's [Account Portal sign-in page](/docs/customization/account-portal/overview#sign-in) to sign in the user. - `https://your-site.com/welcome?token=THE_TOKEN` + If you would rather use your own sign-in page, you can use the `token` property that is returned. Add the `token` as a query param in any link, such as the following example: - You can embedded this link anywhere, such as an email. + `https://your-site.com/accept-token?token=` + + Then, you can embed this link anywhere, such as an email. ### Build a custom flow for signing in with a sign-in token - To do something with the email links you generate, you must setup a page in your frontend that detects the sign-in token, signs the user in, then performs whatever actions you want. + To handle email links with sign-in tokens, you must set up a page in your frontend that detects the token, signs the user in, and performs any additional actions you need. - The following example demonstrates basic code that detects a token and uses it to initiate a sign-in with Clerk: + The following example demonstrates basic code that detects a token in the URL query params and uses it to initiate a sign-in with Clerk: @@ -53,47 +55,60 @@ This guide will teach you how to set this flow up. import { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' - export default function AcceptToken() { + export default function Page() { + const [loading, setLoading] = useState(false) const { signIn, setActive } = useSignIn() const { user } = useUser() - const [signInProcessed, setSignInProcessed] = useState(false) + + // Get the token from the query params const signInToken = useSearchParams().get('token') useEffect(() => { - if (!signIn || !setActive || !signInToken) { + if (!signIn || !setActive || !signInToken || user || loading) { return } const createSignIn = async () => { + setLoading(true) try { - // Create a signIn with the token. - // Note that you need to use the "ticket" strategy. - const res = await signIn.create({ + // Create the `SignIn` with the token + const signInAttempt = await signIn.create({ strategy: 'ticket', ticket: signInToken as string, }) - setActive({ - session: res.createdSessionId, - beforeEmit: () => setSignInProcessed(true), - }) + + // If the sign-in was successful, set the session to active + if (signInAttempt.status === 'complete') { + setActive({ + session: signInAttempt.createdSessionId, + }) + } else { + // If the sign-in attempt is not complete, check why. + // User may need to complete further steps. + console.error(JSON.stringify(signInAttempt, null, 2)) + } } catch (err) { - setSignInProcessed(true) + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + } finally { + setLoading(false) } } createSignIn() - }, [signIn, setActive]) + }, [signIn, setActive, signInToken, user, loading]) if (!signInToken) { - return
no token provided
+ return
No token provided.
} - if (!signInProcessed) { - return
loading
+ if (!user) { + return null } - if (!user) { - return
error invalid token
+ if (loading) { + return
Signing you in...
} return
Signed in as {user.id}
@@ -105,59 +120,71 @@ This guide will teach you how to set this flow up. import { useUser, useSignIn } from '@clerk/nextjs' import { useEffect, useState } from 'react' - // Grab the query param server side, and pass through props + // Get the token from the query param server side, and pass through props export const getServerSideProps: GetServerSideProps = async (context) => { return { props: { signInToken: context.query.token ? context.query.token : null }, } } - const AcceptToken = ({ signInToken }: InferGetServerSidePropsType) => { + export default function AcceptTokenPage({ + signInToken, + }: InferGetServerSidePropsType) { + const [loading, setLoading] = useState(false) const { signIn, setActive } = useSignIn() const { user } = useUser() - const [signInProcessed, setSignInProcessed] = useState(false) useEffect(() => { - if (!signIn || !setActive || !signInToken) { + if (!signIn || !setActive || !signInToken || user || loading) { return } const createSignIn = async () => { + setLoading(true) try { - // Create a signIn with the token. - // Note that you need to use the "ticket" strategy. - const res = await signIn.create({ + // Create the `SignIn` with the token + const signInAttempt = await signIn.create({ strategy: 'ticket', ticket: signInToken as string, }) - setActive({ - session: res.createdSessionId, - beforeEmit: () => setSignInProcessed(true), - }) + + // If the sign-in was successful, set the session to active + if (signInAttempt.status === 'complete') { + setActive({ + session: signInAttempt.createdSessionId, + }) + } else { + // If the sign-in attempt is not complete, check why. + // User may need to complete further steps. + console.error(JSON.stringify(signInAttempt, null, 2)) + } } catch (err) { - setSignInProcessed(true) + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + setLoading(true) + } finally { + setLoading(false) } } createSignIn() - }, [signIn, setActive]) + }, [signIn, setActive, signInToken, user, loading]) if (!signInToken) { - return
no token provided
+ return
No token provided.
} - if (!signInProcessed) { - return
loading
+ if (loading) { + return
Loading...
} if (!user) { - return
error invalid token
+ return null } return
Signed in as {user.id}
} - - export default AcceptToken ```
diff --git a/docs/custom-flows/invitations.mdx b/docs/custom-flows/invitations.mdx index 7b5346a027..dd578bf306 100644 --- a/docs/custom-flows/invitations.mdx +++ b/docs/custom-flows/invitations.mdx @@ -1,45 +1,56 @@ --- -title: Build a custom sign-up flow to handle application invitations -description: Learn how to use Clerk's API to build a custom sign-up flow for handling application invitations. +title: Sign-up with application invitations +description: Learn how to use Clerk's API to build a custom flow for handling application invitations. --- > [!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). -Inviting users to your Clerk application begins with creating an invitation for an email address. Once the invitation is created, an email with an invitation link will be sent to the user's email address. When the user visits the invitation link, they will be redirected to the application's sign up page and **their email address will be automatically verified.** +When a user visits an [invitation](/docs/users/invitations) link, and no custom redirect URL was specified, then they will be redirected to Clerk's [Account Portal sign-up page](/docs/customization/account-portal/overview#sign-up) and **their email address will be automatically verified.** -To learn how to create an invitation, see the [Invitations](/docs/users/invitations) guide. +However, if you specified [a redirect URL when creating the invitation](/docs/users/invitations#redirect-url), you must handle the sign-up flow in your code for that page. You can either embed Clerk's [``](/docs/components/authentication/sign-up) component on that page, or if the prebuilt component doesn't meet your specific needs or if you require more control over the logic, you can rebuild the existing Clerk flows using the Clerk API. This guide will demonstrate how to build a custom sign-up flow to handle application invitations. ## Create the sign-up flow -Once the user visits the invitation link and is redirected to the specified URL, an invitation token will be appended to the URL. To create a sign-up flow using the invitation token, you need to extract the token from the URL and pass it to the [`signUp.create`](/docs/references/javascript/sign-up/sign-up#create) method. +Once the user visits the invitation link and is redirected to the specified URL, the query parameter `__clerk_ticket` will be appended to the URL. This query parameter contains the invitation token. -The following example demonstrates how to create a new sign-up using the invitation token. If there is no invitation token in the URL, the user will be shown a message indicating that they need an invitation to sign up for the application. +For example, if the redirect URL was `https://www.example.com/accept-invitation`, the URL that the user would be redirected to would be `https://www.example.com/accept-invitation?__clerk_ticket=.....`. + +To create a sign-up flow using the invitation token, you need to extract the token from the URL and pass it to the [`signUp.create()`](/docs/references/javascript/sign-up/sign-up#create) method, as shown in the following example. The following example also demonstrates how to collect additional user information for the sign-up; you can either remove these fields or adjust them to fit your application. - ```tsx {{ filename: 'app/sign-up/[[...sign-up]]/page.tsx' }} + ```tsx {{ filename: 'app/accept-invitation/page.tsx' }} 'use client' import * as React from 'react' - import { useSignUp } from '@clerk/nextjs' - import { useRouter } from 'next/navigation' + import { useSignUp, useUser } from '@clerk/nextjs' + import { useSearchParams, useRouter } from 'next/navigation' export default function Page() { + const { user } = useUser() + const router = useRouter() const { isLoaded, signUp, setActive } = useSignUp() const [firstName, setFirstName] = React.useState('') const [lastName, setLastName] = React.useState('') - const router = useRouter() + const [password, setPassword] = React.useState('') + + // Handle signed-in users visiting this page + // This will also redirect the user once they finish the sign-up process + React.useEffect(() => { + if (user?.id) { + router.push('/') + } + }, [user]) - // Get the token from the query parameter - const param = '__clerk_ticket' - const ticket = new URL(window.location.href).searchParams.get(param) + // Get the token from the query params + const token = useSearchParams().get('__clerk_ticket') - // If there is no invitation token, restrict access to the sign-up page - if (!ticket) { - return

You need an invitation to sign up for this application.

+ // If there is no invitation token, restrict access to this page + if (!token) { + return

No invitation token found.

} // Handle submission of the sign-up form @@ -49,11 +60,7 @@ The following example demonstrates how to create a new sign-up using the invitat if (!isLoaded) return try { - if (!ticket) return null - - // Optional: collect additional user information - const firstName = 'John' - const lastName = 'Doe' + if (!token) return null // Create a new sign-up with the supplied invitation token. // Make sure you're also passing the ticket strategy. @@ -61,27 +68,25 @@ The following example demonstrates how to create a new sign-up using the invitat // automatically verified because of the invitation token. const signUpAttempt = await signUp.create({ strategy: 'ticket', - ticket, + ticket: token, firstName, lastName, + password, }) - // If verification was completed, set the session to active - // and redirect the user + // If the sign-up was completed, set the session to active if (signUpAttempt.status === 'complete') { await setActive({ session: signUpAttempt.createdSessionId }) - router.push('/') } else { // If the status is not complete, check why. User may need to // complete further steps. console.error(JSON.stringify(signUpAttempt, null, 2)) } - } catch (err: any) { + } catch (err) { console.error(JSON.stringify(err, null, 2)) } } - // Display the initial sign-up form to capture optional sign-up info return ( <>

Sign up

@@ -106,6 +111,16 @@ The following example demonstrates how to create a new sign-up using the invitat onChange={(e) => setLastName(e.target.value)} /> +
+ + setPassword(e.target.value)} + /> +
@@ -137,6 +152,8 @@ The following example demonstrates how to create a new sign-up using the invitat + + @@ -165,7 +182,7 @@ The following example demonstrates how to create a new sign-up using the invitat } else { // Get the token from the query parameter const param = '__clerk_ticket' - const ticket = new URL(window.location.href).searchParams.get(param) + const token = new URL(window.location.href).searchParams.get(param) // Handle the sign-up form document.getElementById('sign-up-form').addEventListener('submit', async (e) => { @@ -174,14 +191,16 @@ The following example demonstrates how to create a new sign-up using the invitat const formData = new FormData(e.target) const firstName = formData.get('firstName') const lastName = formData.get('lastName') + const password = formData.get('password') try { // Start the sign-up process using the ticket method const signUpAttempt = await clerk.client.signUp.create({ strategy: 'ticket', - ticket, + ticket: token, firstName, lastName, + password, }) // If sign-up was successful, set the session to active @@ -192,10 +211,10 @@ The following example demonstrates how to create a new sign-up using the invitat // complete further steps. console.error(JSON.stringify(signUpAttempt, null, 2)) } - } catch (error) { + } catch (err) { // See https://clerk.com/docs/custom-flows/error-handling // for more info on error handling - console.error(JSON.stringify(error, null, 2)) + console.error(JSON.stringify(err, null, 2)) } }) } @@ -221,6 +240,8 @@ The following example demonstrates how to create a new sign-up using the invitat + + @@ -249,7 +270,7 @@ The following example demonstrates how to create a new sign-up using the invitat } else { // Get the token from the query parameter const param = '__clerk_ticket' - const ticket = new URL(window.location.href).searchParams.get(param) + const token = new URL(window.location.href).searchParams.get(param) // Handle the sign-up form document.getElementById('sign-up-form').addEventListener('submit', async (e) => { @@ -258,14 +279,16 @@ The following example demonstrates how to create a new sign-up using the invitat const formData = new FormData(e.target) const firstName = formData.get('firstName') const lastName = formData.get('lastName') + const password = formData.get('password') try { // Start the sign-up process using the ticket method const signUpAttempt = await Clerk.client.signUp.create({ strategy: 'ticket', - ticket, + ticket: token, firstName, lastName, + password, }) // If sign-up was successful, set the session to active @@ -276,10 +299,10 @@ The following example demonstrates how to create a new sign-up using the invitat // complete further steps. console.error(JSON.stringify(signUpAttempt, null, 2)) } - } catch (error) { + } catch (err) { // See https://clerk.com/docs/custom-flows/error-handling // for more info on error handling - console.error(JSON.stringify(error, null, 2)) + console.error(JSON.stringify(err, null, 2)) } }) } diff --git a/docs/manifest.json b/docs/manifest.json index 81caad884e..49ad324d99 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -613,9 +613,13 @@ "href": "/docs/organizations/updating-organizations" }, { - "title": "Invite users", + "title": "Invite users to an organization", "href": "/docs/organizations/inviting-users" }, + { + "title": "Accept organization invitations", + "href": "/docs/organizations/accept-organization-invitations" + }, { "title": "Manage member roles", "href": "/docs/organizations/managing-roles" diff --git a/docs/organizations/accept-organization-invitations.mdx b/docs/organizations/accept-organization-invitations.mdx new file mode 100644 index 0000000000..efaf5f77fc --- /dev/null +++ b/docs/organizations/accept-organization-invitations.mdx @@ -0,0 +1,181 @@ +--- +title: Accept organization invitations +description: Learn how to use Clerk's API to build a custom flows for handling organization invitations. +--- + +> [!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). + +When a user visits an [organization invitation](/docs/organizations/invitations) link, and no custom redirect URL was specified, and they have an account for your application, then they will be redirected to Clerk's [Account Portal sign-in page](/docs/customization/account-portal/overview#sign-in). If they do not have an account for your application, they will be redirected to Clerk's [Account Portal sign-up page](/docs/customization/account-portal/overview#sign-up). + +However, if you specified [a redirect URL when creating the invitation](/docs/users/invitations#redirect-url), you must handle the sign-up and sign-in flows in your code for that page. You can either embed Clerk's [`](/docs/components/authentication/sign-in) component on that page, or if the prebuilt component doesn't meet your specific needs or if you require more control over the logic, you can rebuild the existing Clerk flows using the Clerk API. + +This guide will demonstrate how to build custom flows to handle organization invitations. + +## Create the sign-up flow + +Once the user visits the invitation link and is redirected to the specified URL, the query parameters `__clerk_ticket` and `__clerk_status` will be appended to the URL. + +For example, if the redirect URL was `https://www.example.com/accept-invitation`, the URL that the user would be redirected to would be `https://www.example.com/accept-invitation?__clerk_ticket=.....`. + +The `__clerk_ticket` query parameter contains the ticket token, which is essential for completing the organization invitation flow. You'll use this token in your code for the page that you redirected the user to. + +The `__clerk_status` query parameter is the outcome of the ticket verification and will contain one of three values: + +- `sign_in` indicates the user already exists in your application. You should create a sign-in flow using the invitation token by extracting the token from the URL and passing it to the [`signIn.create()`](/docs/references/javascript/sign-in/sign-in#create) method. +- `sign_up` indicates the user doesn't already exist in your application. You should create a sign-up flow using the invitation token by extracting the token from the URL and passing it to the [`signUp.create()`](/docs/references/javascript/sign-up/sign-up#create) method. +- `complete` indicates the user already exists in your application, and was signed in. The flow has been completed and no further actions are required. + +The following example demonstrates how to handle both sign-up and sign-in flows using the invitation token: + +1. It extracts the token from the URL. +1. It passes the token to either [`signUp.create()`](/docs/references/javascript/sign-up/sign-up#create) or [`signIn.create()`](/docs/references/javascript/sign-in/sign-in#create), depending on the `__clerk_status`. +1. It includes optional fields for collecting additional user information during sign-up. You can modify or remove these fields as needed for your application. + + + + ```tsx {{ filename: 'app/accept-invitation/page.tsx' }} + 'use client' + + import * as React from 'react' + import { useOrganization, useSignIn, useSignUp } from '@clerk/nextjs' + import { useSearchParams } from 'next/navigation' + + export default function Page() { + const { isLoaded, signUp, setActive: setActiveSignUp } = useSignUp() + const { signIn, setActive: setActiveSignIn } = useSignIn() + const { organization } = useOrganization() + const [firstName, setFirstName] = React.useState('') + const [lastName, setLastName] = React.useState('') + const [password, setPassword] = React.useState('') + + // Get the token and account status from the query params + const token = useSearchParams().get('__clerk_ticket') + const accountStatus = useSearchParams().get('__clerk_status') + + // If there is no invitation token, restrict access to this page + if (!token) { + return

No invitation token found.

+ } + + // Handle sign-in + React.useEffect(() => { + if (!signIn || !setActiveSignIn || !token || organization || accountStatus !== 'sign_in') { + return + } + + const createSignIn = async () => { + try { + // Create a new `SignIn` with the supplied invitation token. + // Make sure you're also passing the ticket strategy. + const signInAttempt = await signIn.create({ + strategy: 'ticket', + ticket: token as string, + }) + + // If the sign-in was successful, set the session to active + if (signInAttempt.status === 'complete') { + await setActiveSignIn({ + session: signInAttempt.createdSessionId, + }) + } else { + // If the sign-in attempt is not complete, check why. + // User may need to complete further steps. + console.error(JSON.stringify(signInAttempt, null, 2)) + } + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + } + } + + createSignIn() + }, [signIn]) + + // Handle submission of the sign-up form + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault() + + if (!isLoaded) return + + try { + // Create a new sign-up with the supplied invitation token. + // Make sure you're also passing the ticket strategy. + // After the below call, the user's email address will be + // automatically verified because of the invitation token. + const signUpAttempt = await signUp.create({ + strategy: 'ticket', + ticket: token, + firstName, + lastName, + password, + }) + + // If the sign-up was successful, set the session to active + if (signUpAttempt.status === 'complete') { + await setActiveSignUp({ session: signUpAttempt.createdSessionId }) + } else { + // If the sign-in attempt is not complete, check why. + // User may need to complete further steps. + console.error(JSON.stringify(signUpAttempt, null, 2)) + } + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error(JSON.stringify(err, null, 2)) + } + } + + if (accountStatus === 'sign_in' && !organization) { + return
Signing you in...
+ } + + if (accountStatus === 'sign_up' && !organization) { + return ( + <> +

Sign up

+
+
+ + setFirstName(e.target.value)} + /> +
+
+ + setLastName(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+
+ +
+
+ + ) + } + + return
Organization invitation accepted!
+ } + ``` +
+
diff --git a/docs/organizations/invitations.mdx b/docs/organizations/invitations.mdx new file mode 100644 index 0000000000..ee36ab07bf --- /dev/null +++ b/docs/organizations/invitations.mdx @@ -0,0 +1,135 @@ +--- +title: Invite users to your organization +description: Learn how to invite users to your organization. +--- + +Organization invitations allow you to add new members to your organization, granting them access to organization-specific features and resources. + +Once you create an invitation, Clerk sends an email to the invited user with a unique invitation link. When the user visits the organization invitation link, they will be redirected to Clerk's [Account Portal sign-in page](/docs/customization/account-portal/overview#sign-in). If the user is already signed in, they will be redirected to your application's homepage (`/`). If you want to redirect the user to a specific page in your application, you can [specify a redirect URL when creating the invitation.](#redirect-url) + +## Create an invitation + +Clerk's [prebuilt components](/docs/components/overview) and [Account Portal pages](/docs/customization/account-portal/overview) manage all organization invitation flows, including creating, managing, and accepting invitations. + +However, if you want to build custom flows, see the following sections. + +### Client-side + +To create an organization invitation on the client-side, see the [dedicated guide](/docs/organizations/inviting-users). Note that this uses the [`organizations.inviteMember()`](/docs/references/javascript/organization/invitations#invite-member) method, which does not allow you to specify a redirect URL; it will always redirect to the Account Portal sign-in page. If you want to specify a redirect URL, you must create the invitation on the server-side. + +### Server-side + +You can also create organization invitations via the [Backend API](/docs/reference/backend-api/tag/Organization-Invitations#operation/CreateOrganizationInvitation){{ target: '_blank' }} either by using a cURL command or Clerk's [JavaScript Backend SDK](/docs/references/backend/overview). Clerk's JavaScript Backend SDK is a wrapper around the Backend API that makes it easier to interact with the API. + +Use the following tabs to see examples for each method. + + + + The following example demonstrates how to create an organization invitation using cURL. + + + Replace the `` with the ID of the organization you want to invite the user to. Replace the `user_123` with the ID of the user who is inviting the other user. Replace the email address with the email address you want to invite. Your secret key is already injected into the code snippet. + + + + Replace the `` with the ID of the organization you want to invite the user to. Replace the `user_123` with the ID of the user who is inviting the other user. Replace the email address with the email address you want to invite. Replace `YOUR_SECRET_KEY` with your Clerk secret key. You can find your secret key in the Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. + + + ```bash {{ filename: 'terminal' }} + curl 'https://api.clerk.com/v1/organizations//invitations' \ + -X POST \ + -H 'Authorization: Bearer {{secret}}' \ + -H 'Content-Type: application/json' \ + -d '{ "inviter_user_id": "user_123", "email_address": "test@gmail.com", "role": "org:member" }' + ``` + + + + To use the Backend SDK to create an invitation, see the [`createOrganizationInvitation()`](/docs/references/backend/organization/create-organization-invitation) reference documentation. + + + +Check out [the Backend API reference](https://clerk.com/docs/reference/backend-api/tag/Organization-Invitations#operation/CreateOrganizationInvitation) to see an example of the response. + +### Redirect URL + +When you create an invitation, you can specify a `redirect_url` parameter. This parameter tells Clerk where to redirect the user when they visit the invitation link. + +The following example demonstrates how to use cURL to create an invitation with the `redirect_url` set to `https://www.example.com/accept-invitation`: + +```bash +curl 'https://api.clerk.com/v1/organizations//invitations' \ + -X POST \ + -H 'Authorization: Bearer {{secret}}' \ + -H 'Content-Type: application/json' \ + -d '{ "inviter_user_id": "user_123", "email_address": "test@gmail.com", "role": "org:member", "redirect_url": "https://www.example.com/accept-invitation" }' +``` + +Once the user visits the invitation link, they will be redirected to the page you specified. On that page, you must handle the authentication flow in your code. You can either embed Clerk's [`](/docs/components/authentication/sign-in) component or, if the prebuilt component doesn't meet your needs or you require more control over the logic, you can build a [custom flow](/docs/organizations/accept-organization-invitations). + +> [!TIP] +> For testing redirect URLs in your development environment, you can pass your port (`http://localhost:3000`). If you'd like to use Clerk's Account Portal, pass your Clerk Frontend API URL as the base URL. For example, `https://prepared-phoenix-98.clerk.accounts.dev/sign-up` redirects the user to the Account Portal sign-up page. You can find your Frontend API URL in the Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. In the left sidebar, select **Show API URLs**. + +### Invitation metadata + +You can also add metadata to an invitation when creating the invitation through the Backend API. Once the invited user signs up using the invitation link, the invitation metadata (`OrganizationInvitation.public_metadata`) will be stored in the user's metadata (`User.public_metadata`). You can find more information about user metadata in the [metadata](/docs/users/metadata) docs. + +To add metadata to an invitation, you can use the `public_metadata` property when the invitation is created. + +The following example demonstrates how to create an invitation with metadata using cURL. + + + Replace the `` with the ID of the organization you want to invite the user to. Replace the `user_123` with the ID of the user who is inviting the other user. Replace the email address with the email address you want to invite. Your secret key is already injected into the code snippet. + + + + Replace the `` with the ID of the organization you want to invite the user to. Replace the `user_123` with the ID of the user who is inviting the other user. Replace the email address with the email address you want to invite. Replace `YOUR_SECRET_KEY` with your Clerk secret key. You can find your secret key in the Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. + + +```bash +curl 'https://api.clerk.com/v1/organizations//invitations' \ + -X POST \ + -H 'Authorization: Bearer {{secret}}' \ + -H 'Content-Type: application/json' \ + -d '{ "inviter_user_id": "user_123", "email_address": "test@gmail.com", "role": "org:member", "public_metadata": {"age": "21"} }' +``` + +## Revoke an invitation + +Revoking an invitation prevents the user from using the invitation link that was sent to them. + +### Client-side + +To revoke an invitation client-side, see the [dedicated guide](/docs/organizations/inviting-users). + +### Server-side + +To revoke an invitation server-side, you can use the [Backend API](/docs/reference/backend-api/tag/Organization-Invitations#operation/RevokeOrganizationInvitation){{ target: '_blank' }}. + +You can either use a cURL command or Clerk's [JavaScript Backend SDK](/docs/references/backend/overview) to create an invitation. Use the following tabs to see examples for each method. + + + + The following example demonstrates how to revoke an invitation using cURL. + + + Replace the `` with the ID of the organization you want to revoke the invitation from. Replace the `` with the ID of the invitation you want to revoke. Replace `user_123` with the ID of the user who is revoking the invitation. Your secret key is already injected into the code snippet. + + + + Replace the `` with the ID of the organization you want to revoke the invitation from. Replace the `` with the ID of the invitation you want to revoke. Replace `user_123` with the ID of the user who is revoking the invitation. Replace `YOUR_SECRET_KEY` with your Clerk secret key. You can find your secret key in the Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. + + + ```bash {{ filename: 'terminal' }} + curl 'https://api.clerk.com/v1/organizations//invitations//revoke' \ + -X POST \ + -H 'Authorization: Bearer {{secret}}' \ + -H 'Content-Type: application/json' \ + -d '{ "requesting_user_id": "user_123" }' + ``` + + + + To use the Backend SDK to revoke an organization invitation, see the [`revokeOrganizationInvitation()`](/docs/references/backend/organization/revoke-organization-invitation) reference documentation. + + diff --git a/docs/organizations/inviting-users.mdx b/docs/organizations/inviting-users.mdx index 78f665f984..8678c95661 100644 --- a/docs/organizations/inviting-users.mdx +++ b/docs/organizations/inviting-users.mdx @@ -1,16 +1,21 @@ --- -title: Build a custom flow for inviting users to an organization -description: Learn how to use Clerk's API to build a custom flow for inviting users to an organization. +title: Build a custom flow for creating and managing organization invitations +description: Learn how to use Clerk's API to build a custom flow for creating and managing organization invitations. --- +{/* TODO: POST-IA rename this file. Don't do right now because the sidebar is going to be changed for the IA anyways, and it's one less redirect we have to deal with. */} + > [!CAUTION] > This guide is for users who want to build a _custom_ user interface using the Clerk API. To invite users to an organization using a _prebuilt_ UI, you should use Clerk's [prebuilt components](/docs/components/overview). -Organization members with appropriate [permissions](/docs/organizations/roles-permissions) can invite new users to their organization and manage those invitations. When an administrator invites a new member, an invitation email is sent out. The invitation recipient can be either an existing user of your application or a new user. If the latter is true, the user will need to register in order to accept the invitation. +Organization members with appropriate [permissions](/docs/organizations/roles-permissions) can invite new users to their organization and manage those invitations. The invitation recipient can be either an existing user of your application or a new user. If they are a new user, they will need to sign up in order to accept the invitation. Users with the appropriate permissions can also revoke organization invitations for users that have not yet joined, which will prevent the user from becoming an organization member. -This guide will demonstrate how to use Clerk's API to build a custom flow for inviting users to an organization and listing an organization's pending invitations. +This guide will demonstrate how to use Clerk's API to build a custom flow for inviting users to an organization and managing an organization's pending invitations. + +> [!NOTE] +> This guide is for creating and managing organization invitations client-side. You can also create an organization invitation using the Backend API. See the [organization invitations reference](/docs/organizations/invitations) for more information. @@ -384,26 +389,6 @@ This guide will demonstrate how to use Clerk's API to build a custom flow for in -## Custom redirect URL - -When creating an organization invitation and using Clerk's Next.js, Remix, or Backend SDKs, you can specify a custom redirect URL. After users click on organization invitation link and the ticket is verified, they will get redirected to that URL. The URL will contain two important query parameters added by Clerk: `__clerk_ticket` and `__clerk_status`. - -The `__clerk_ticket` query parameter will hold the actual ticket token, which can be used during sign-in and sign-up flows in order to complete the organization invitation flow. - -The `__clerk_status` query parameter is the outcome of the ticket verification and will contain one of three values: - -- `sign_in` indicates the user already exists in your application. You should create a sign-in ticket in order to complete the flow. -- `sign_up` indicates the user doesn't already exist in your application. You should create a sign-up ticket in order to complete the flow. -- `complete` indicates the user already exists in your application, and was signed in. The flow has been completed and no further actions are required. - -An example implementation on how to create an invitation by providing a redirect url using the [JavaScript Backend SDK](/docs/references/backend/overview): +## Next steps -```ts -clerkClient.organizations.createOrganizationInvitation({ - organizationId: 'org_2S7G8yGKaPp7nWn52idDTnxXkWW', - emailAddress: 'member@myapp.com', - inviterUserId: 'user_2ULtoAaFHBSep6Gnr5bphZXITmD', - role: 'org:member', - redirectUrl: 'https://myapp.com/invite-accepted', -}) -``` +Now that you've created a flow for managing organization invitations, you might want to create a flow for accepting invitations. See the [dedicated custom flow guide](/docs/organizations/accept-organization-invitations) for more information. diff --git a/docs/users/invitations.mdx b/docs/users/invitations.mdx index 6083aaf220..2525fbf5d9 100644 --- a/docs/users/invitations.mdx +++ b/docs/users/invitations.mdx @@ -1,27 +1,29 @@ --- -title: Invitations +title: Invite users to your application description: Learn how to invite users to your Clerk application. --- -Inviting users to your Clerk application begins with creating an invitation for an email address. Once the invitation is created, an email with an invitation link will be sent to the user's email address. When the user visits the invitation link, they will be redirected to the application's sign up page and **their email address will be automatically verified.** +Inviting users to your Clerk application allows you to onboard new users seamlessly by sending them a unique invitation link. + +Once you create an invitation, Clerk sends an email to the invited user with a unique invitation link. When the user visits the invitation link, they will be redirected to Clerk's [Account Portal sign-up page](/docs/customization/account-portal/overview#sign-up) and **their email address will be automatically verified.** If you want to redirect the user to a specific page in your application, you can [specify a redirect URL when creating the invitation.](#redirect-url) Invitations expire after a month. If the user clicks on an expired invitation, they will get redirected to the application's sign-up page and will have to go through the normal sign-up flow. Their email address will not be auto-verified. > [!TIP] -> Invitations are only used to invite users to your application. The application will still be available to everyone even without an invitation. If you're looking into creating invitation-only applications, please refer to our [restrictions](/docs/authentication/configuration/restrictions) options. +> Invitations are only used to invite users to your application. The application will still be available to everyone even without an invitation. If you're looking to create an invitation-only application, please refer to Clerk's [restrictions](/docs/authentication/configuration/restrictions) options. -## Creating invitations +## Create an invitation -At the moment, you can only create invitations for email addresses via the [Backend API](/docs/reference/backend-api/tag/Invitations#operation/CreateInvitation){{ target: '_blank' }}. +At the moment, invitations can only be created for for email addresses and can only be created via the [Backend API](/docs/reference/backend-api/tag/Invitations#operation/CreateInvitation){{ target: '_blank' }}. -You can either use a cURL command or Clerk's [JavaScript Backend SDK](/docs/references/backend/overview) to create an invitation. Use the following tabs to see examples for each method. +You can either use a cURL command or Clerk's [JavaScript Backend SDK](/docs/references/backend/overview) to create an invitation. Clerk's JavaScript Backend SDK is a wrapper around the Backend API that makes it easier to interact with the API. Use the following tabs to see examples for each method. The following example demonstrates how to create an invitation using cURL. - Replace the email address with the email address you want to invite. Your secret key is already injected into the code snippet. + Replace the email address with the email address you want to invite. Your Clerk secret key is already injected into the code snippet. @@ -34,32 +36,26 @@ You can either use a cURL command or Clerk's [JavaScript Backend SDK](/docs/refe - Clerk's [JavaScript Backend SDK](/docs/references/backend/overview) is a wrapper around the Backend API that makes it easier to interact with the API. - - To use the Backend SDK to create an invitation, see the [`createInvitation()`](/docs/references/backend/invitations/create-invitation) reference documentation. + To use the Backend SDK to create an invitation, see the [`createInvitation()` reference.](/docs/references/backend/invitations/create-invitation) +Check out [the Backend API reference](https://clerk.com/docs/reference/backend-api/tag/Invitations#operation/CreateInvitation) to see an example of the response. + ### Redirect URL When you create an invitation, you can specify a `redirect_url` parameter. This parameter tells Clerk where to redirect the user when they visit the invitation link. -The following example demonstrates how to use cURL to create an invitation with the `redirect_url` set to `https://www.example.com/sign-up`: +The following example demonstrates how to use cURL to create an invitation with the `redirect_url` set to `https://www.example.com/accept-invitation`: ```bash -curl https://api.clerk.com/v1/invitations -X POST -d '{"email_address": "email@example.com", "redirect_url": "https://www.example.com/sign-up"}' -H "Authorization:Bearer {{secret}}" -H 'Content-Type:application/json' +curl https://api.clerk.com/v1/invitations -X POST -d '{"email_address": "email@example.com", "redirect_url": "https://www.example.com/accept-invitation"}' -H "Authorization:Bearer {{secret}}" -H 'Content-Type:application/json' ``` -Once the user visits the invitation link and is redirected to the specified URL, an invitation token will be appended to the URL. - -Using the previous example, the URL with the invitation token would look like this: - -`https://www.example.com/sign-up?__clerk_ticket=.....` - -You can then use the invitation token to create a new sign-up. +Once the user visits the invitation link, they will be redirected to the page you specified, which means you must handle the sign-up flow in your code for that page. You can either embed Clerk's [``](/docs/components/authentication/sign-up) component on that page, or if the prebuilt component doesn't meet your specific needs or if you require more control over the logic, you can build a [custom flow](/docs/custom-flows/invitations). > [!TIP] -> For creating invitations in a development environment, you can pass your Clerk Frontend API URL as the base URL. For example, your redirect URL could look like `https://prepared-phoenix-98.clerk.accounts.dev/sign-up`. You can find your Frontend API URL in the Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. On the left side, select **Show API URLs**. +> For testing redirect URL's in your development environment, you can pass your port (`http://localhost:3000`). If you would like to use Clerk's Account Portal, you can pass your Clerk Frontend API URL as the base URL. For example, `https://prepared-phoenix-98.clerk.accounts.dev/sign-up` would send the user to the Account Portal sign-up page. You can find your Frontend API URL in the Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page. On the left side, select **Show API URLs**. ### Invitation metadata @@ -107,8 +103,6 @@ You can either use a cURL command or Clerk's [JavaScript Backend SDK](/docs/refe
- Clerk's [JavaScript Backend SDK](/docs/references/backend/overview) is a wrapper around the Backend API that makes it easier to interact with the API. - To use the Backend SDK to revoke an invitation, see the [`revokeInvitation()`](/docs/references/backend/invitations/revoke-invitation) reference documentation.