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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/_partials/custom-flows/future-api-callout.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
> [!IMPORTANT]
> The APIs described here are stable, and will become the default in the next major version of `clerk-js`.
12 changes: 12 additions & 0 deletions docs/_partials/custom-flows/sso-connections-legacy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
The following example **will both sign up _and_ sign in users**, eliminating the need for a separate sign-up page. However, if you want to have separate sign-up and sign-in pages, the sign-up and sign-in flows are equivalent, meaning that all you have to do is swap out the `SignIn` object for the `SignUp` object using the [`useSignUp()`](/docs/reference/hooks/use-sign-up) hook.

The following example:

1. Accesses the [`SignIn`](/docs/reference/javascript/sign-in) object using the [`useSignIn()`](/docs/reference/hooks/use-sign-in) hook.
1. Starts the authentication process by calling [`SignIn.authenticateWithRedirect(params)`](/docs/reference/javascript/sign-in#authenticate-with-redirect). This method requires a `redirectUrl` param, which is the URL that the browser will be redirected to once the user authenticates with the identity provider.
1. Creates a route at the URL that the `redirectUrl` param points to. The following example names this route `/sso-callback`. This route should either render the prebuilt [`<AuthenticateWithRedirectCallback/>`](/docs/reference/components/control/authenticate-with-redirect-callback) component or call the [`Clerk.handleRedirectCallback()`](/docs/reference/javascript/clerk#handle-redirect-callback) method if you're not using the prebuilt component.

The following example shows two files:

1. The sign-in page where the user can start the authentication flow.
1. The SSO callback page where the flow is completed.
11 changes: 4 additions & 7 deletions docs/_partials/custom-flows/sso-connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ The following example **will both sign up _and_ sign in users**, eliminating the
The following example:

1. Accesses the [`SignIn`](/docs/reference/javascript/sign-in) object using the [`useSignIn()`](/docs/reference/hooks/use-sign-in) hook.
1. Starts the authentication process by calling [`SignIn.authenticateWithRedirect(params)`](/docs/reference/javascript/sign-in#authenticate-with-redirect). This method requires a `redirectUrl` param, which is the URL that the browser will be redirected to once the user authenticates with the identity provider.
1. Creates a route at the URL that the `redirectUrl` param points to. The following example names this route `/sso-callback`. This route should either render the prebuilt [`<AuthenticateWithRedirectCallback/>`](/docs/reference/components/control/authenticate-with-redirect-callback) component or call the [`Clerk.handleRedirectCallback()`](/docs/reference/javascript/clerk#handle-redirect-callback) method if you're not using the prebuilt component.

The following example shows two files:

1. The sign-in page where the user can start the authentication flow.
1. The SSO callback page where the flow is completed.
1. Starts the authentication process by calling [`SignIn.sso(params)`](/docs/reference/javascript/sign-in-future#sso). This method requires the following params:
- `redirectUrl`: The URL that the browser will be redirected to once the user authenticates with the identity provider if no additional requirements are needed, and a session has been created
- `redirectCallbackUrl`: The URL that the browser will be redirected to once the user authenticates with the identity provider if additional requirements are needed
1. Creates a route at the URL that the `redirectCallbackUrl` param points to. The following example re-uses the `/sign-in` route, which should be written to handle when a sign-in attempt is in a non-complete status such as `needs_second_factor`.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Sign-up with application invitations
description: Learn how to use the Clerk API to build a custom flow for handling application invitations.
sdk: nextjs, react, expo, react-router, tanstack-react-start
---

<Include src="_partials/custom-flows-callout" />
Expand All @@ -19,236 +20,74 @@ Once the user visits the invitation link and is redirected to the specified 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=.....`.

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/reference/javascript/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.
To create a sign-up flow using the invitation token, you need to call the [`signUp.ticket()`](/docs/reference/javascript/sign-up-future#ticket) 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.

<Tabs items={["Next.js", "JavaScript"]}>
<Tabs items={["Next.js"]}>
<Tab>
```tsx {{ filename: 'app/accept-invitation/page.tsx', collapsible: true }}
'use client'

import * as React from 'react'
import { useSignUp, useUser } from '@clerk/nextjs'
import { useSearchParams, useRouter } from 'next/navigation'
import { useRouter } from 'next/navigation'

export default function Page() {
const { isSignedIn, user } = useUser()
const { signUp, errors, fetchStatus } = useSignUp()
const router = useRouter()
const { isLoaded, signUp, setActive } = useSignUp()
const [firstName, setFirstName] = React.useState('')
const [lastName, setLastName] = React.useState('')
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 (isSignedIn) {
router.push('/')
}
}, [isSignedIn])

// Get the token from the query params
const token = useSearchParams().get('__clerk_ticket')

// If there is no invitation token, restrict access to this page
if (!token) {
return <p>No invitation token found.</p>
}
const handleSubmit = async (formData: FormData) => {
const firstName = formData.get('firstName') as string
const lastName = formData.get('lastName') as string
const password = formData.get('password') as string

// Handle submission of the sign-up form
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()

if (!isLoaded) return

try {
if (!token) return null

// 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,
await signUp.ticket({
firstName,
lastName,
password,
})
if (signUp.status === 'complete') {
await signUp.finalize({
navigate: () => {
router.push('/')
},
})

// If the sign-up was completed, set the session to active
if (signUpAttempt.status === 'complete') {
await setActive({ session: signUpAttempt.createdSessionId })
} else {
// If the sign-up status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signUpAttempt, null, 2))
}
} catch (err) {
console.error(JSON.stringify(err, null, 2))
}
}

if (signUp.status === 'complete' || isSignedIn) {
return null
}

return (
<>
<h1>Sign up</h1>
<form onSubmit={handleSubmit}>
<form action={handleSubmit}>
<div>
<label htmlFor="firstName">Enter first name</label>
<input
id="firstName"
type="text"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input id="firstName" type="text" name="firstName" />
{errors.fields.firstName && <p>{errors.fields.firstName.message}</p>}
</div>
<div>
<label htmlFor="lastName">Enter last name</label>
<input
id="lastName"
type="text"
name="lastName"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input id="lastName" type="text" name="lastName" />
{errors.fields.lastName && <p>{errors.fields.lastName.message}</p>}
</div>
<div>
<label htmlFor="password">Enter password</label>
<input
id="password"
type="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<input id="password" type="password" name="password" />
{errors.fields.password && <p>{errors.fields.password.message}</p>}
</div>
<div id="clerk-captcha" />
<div>
<button type="submit">Next</button>
<button type="submit" disabled={fetchStatus === 'fetching'}>
Next
</button>
</div>
</form>
</>
)
}
```
</Tab>

<Tab>
<CodeBlockTabs options={["index.html", "main.js"]}>
```html {{ filename: 'index.html', collapsible: true }}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="signed-in"></div>

<div id="task"></div>

<div id="sign-up">
<h2>Sign up</h2>
<form id="sign-up-form">
<label for="firstName">Enter first name</label>
<input name="firstName" id="firstName" />
<label for="lastName">Enter last name</label>
<input name="lastName" id="lastName" />
<label for="password">Enter password</label>
<input name="password" id="password" />
<button type="submit">Continue</button>
</form>
</div>

<script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
</body>
</html>
```

```js {{ filename: 'main.js', collapsible: true }}
import { Clerk } from '@clerk/clerk-js'

const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

const clerk = new Clerk(pubKey)
await clerk.load()

if (clerk.isSignedIn) {
// Mount user button component
document.getElementById('signed-in').innerHTML = `
<div id="user-button"></div>
`

const userbuttonDiv = document.getElementById('user-button')

clerk.mountUserButton(userbuttonDiv)
} else if (clerk.session.currentTask) {
// Check for pending tasks and display custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
switch (clerk.session.currentTask.key) {
case 'choose-organization': {
document.getElementById('app').innerHTML = `
<div id="task"></div>
`

const taskDiv = document.getElementById('task')

clerk.mountTaskChooseOrganization(taskDiv)
}
}
} else {
// Get the token from the query parameter
const param = '__clerk_ticket'
const token = new URL(window.location.href).searchParams.get(param)

// Handle the sign-up form
document.getElementById('sign-up-form').addEventListener('submit', async (e) => {
e.preventDefault()

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: token,
firstName,
lastName,
password,
})

// If sign-up was successful, set the session to active
if (signUpAttempt.status === 'complete') {
await clerk.setActive({
session: signUpAttempt.createdSessionId,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.log(session?.currentTask)
return
}

await 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) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
})
}
```
</CodeBlockTabs>
</Tab>
</Tabs>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Add bot protection to your custom sign-up flow
description: Learn how to add Clerk's bot protection to your custom sign-up flow.
sdk: nextjs, react, expo, js-frontend, react-router, tanstack-react-start
---

<Include src="_partials/custom-flows-callout" />
Expand Down
Loading