Skip to content

Commit

Permalink
final docs review :fingerscrossed:
Browse files Browse the repository at this point in the history
  • Loading branch information
alexisintech committed Feb 4, 2025
1 parent 01eef70 commit fe5f63f
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 241 deletions.
12 changes: 12 additions & 0 deletions docs/_partials/custom-flows/sso-connections.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/references/react/use-sign-up) hook.

The following example:

1. Accesses the [`SignIn`](/docs/references/javascript/sign-in/sign-in) object using the [`useSignIn()`](/docs/references/react/use-sign-in) hook.
1. Starts the authentication process by calling [`SignIn.authenticateWithRedirect(params)`](/docs/references/javascript/sign-in/authenticate-with#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/components/control/authenticate-with-callback) component or call the [`Clerk.handleRedirectCallback()`](/docs/references/javascript/clerk/handle-navigation#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.
8 changes: 4 additions & 4 deletions docs/_partials/expo/enterprise-sso-custom-flow.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
> [!IMPORTANT]
> Expo supports SAML Enterprise SSO, but does not support EASIE or OIDC.
> Expo supports [SAML](/docs/authentication/enterprise-connections/overview#saml) Enterprise SSO, but does not support [OIDC](/docs/authentication/enterprise-connections/overview#oidc).
The following example demonstrates how to create a custom SSO flow with [SAML](docs/authentication/enterprise-connections/overview#saml). In the following example, when the user selects the "Sign in with SAML" button, they'll be redirected to the provider's authentication page. Once they authenticate, they'll be redirected back to your app. If there are missing requirements, like needing to completeMFA, then the `createdSessionId` will remain `null`, and you'll need to add logic to handle the missing requirements. If there are no missing requirements, then the user will be signed in.

```tsx {{ filename: 'app/(auth)/sign-in.tsx', collapsible: true }}
import React, { useCallback, useEffect, useState } from 'react'
import React, { useEffect, useState } from 'react'
import * as WebBrowser from 'expo-web-browser'
import { useSSO } from '@clerk/clerk-expo'
import { View, Button, TextInput } from 'react-native'
Expand All @@ -31,7 +31,7 @@ export default function Page() {

const { startSSOFlow } = useSSO()

const onPress = useCallback(async () => {
const onPress = async () => {
try {
const { createdSessionId, setActive, signIn, signUp } = await startSSOFlow({
strategy: 'enterprise_sso',
Expand All @@ -52,7 +52,7 @@ export default function Page() {
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}, [])
}

return (
<View>
Expand Down
123 changes: 5 additions & 118 deletions docs/custom-flows/enterprise-connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,7 @@ You must configure your application instance through the Clerk Dashboard for the

## Create the sign-up and sign-in flow

When using Enterprise SSO, **the sign-up and sign-in flows are equivalent.** A successful Enterprise SSO authentication flow consists of the following steps:

1. Access the [`SignIn`](/docs/references/javascript/sign-in/sign-in) or [`SignUp`](/docs/references/javascript/sign-up/sign-up) object using the [`useSignIn()`](/docs/references/react/use-sign-in) or [`useSignUp()`](/docs/references/react/use-sign-up) hook, respectively.
1. Start the Enterprise SSO flow by calling [`SignIn.authenticateWithRedirect(params)`][sign-in-redirect] or [`SignUp.authenticateWithRedirect(params)`][sign-up-redirect]. Both of these methods require a `redirectUrl` param, which is the URL that the browser will be redirected to once the user authenticates with the identity provider.
1. Create a route at the URL that the `redirectUrl` param points to. The following example names this route `/sso-callback`. This route should either call the [`Clerk.handleRedirectCallback()`](/docs/references/javascript/clerk/handle-navigation#handle-redirect-callback) method or render the prebuilt [`<AuthenticateWithRedirectCallback/>`](/docs/components/control/authenticate-with-callback) component.

The following example shows two files:

1. The sign-in page where the user can start the Enterprise SSO flow.
1. The SSO callback page where the flow is completed.

{/* Does the signIn need to be awaited? If so, we should update to use try,catch */}
<Include src="_partials/custom-flows/sso-connections" />

<Tabs items={["Next.js", "Expo"]}>
<Tab>
Expand All @@ -36,14 +25,14 @@ The following example shows two files:
export default function Page() {
const { signIn, isLoaded } = useSignIn()

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

if (!isLoaded) return null

const email = (e.target as HTMLFormElement).email.value

await signIn
signIn
.authenticateWithRedirect({
identifier: email,
strategy: 'enterprise_sso',
Expand Down Expand Up @@ -74,8 +63,8 @@ The following example shows two files:
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs'

export default function Page() {
// Handle the redirect flow by rendering the
// prebuilt AuthenticateWithRedirectCallback component.
// Handle the redirect flow by calling the Clerk.handleRedirectCallback() method
// or rendering the prebuilt <AuthenticateWithRedirectCallback/> component.
// This is the final step in the custom Enterprise SSO flow.
return <AuthenticateWithRedirectCallback />
}
Expand All @@ -87,105 +76,3 @@ The following example shows two files:
<Include src="_partials/expo/enterprise-sso-custom-flow" />
</Tab>
</Tabs>

## Enterprise account transfer flows

{/* TODO(Laura): I believe this is built in with Expo's `useSSO()` hook, so I don't think we need to add Expo example here. Is this correct? Also, I tested the above example with Nextjs: I went to /sign-in, and tried signing in with a non-existent user, and it created the user for me. So is this section even necessary? */}

It is common for users who are authenticating with an enterprise account to use a sign-in button when they mean to sign-up, and vice versa. For those cases, the `SignIn` and `SignUp` objects have a `transferable` status that indicates whether the user can be transferred to the other flow.

**If you would like to keep your sign-in and sign-up flows completely separate, then you can skip this section.**

The following example demonstrates how to handle these cases in your sign-in flow. To apply the same logic to the sign-up flow, simply replace [`signIn.authenticateWithRedirect()`][sign-in-redirect] with [`signUp.authenticateWithRedirect()`][sign-up-redirect] in your code.

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

import * as React from 'react'
import { useSignIn, useSignUp } from '@clerk/nextjs'

export default function Page() {
const { signIn } = useSignIn()
const { signUp, setActive } = useSignUp()

if (!signIn || !signUp) return null

const signInWithEnterpriseSSO = (e: React.FormEvent) => {
e.preventDefault()

const email = (e.target as HTMLFormElement).email.value

signIn
.authenticateWithRedirect({
identifier: email,
strategy: 'enterprise_sso',
redirectUrl: '/sign-in/sso-callback',
redirectUrlComplete: '/',
})
.then((res) => {
console.log(res)
})
.catch((err: any) => {
console.log(err.errors)
console.error(err, null, 2)
})
}

async function handleSignIn(e: React.FormEvent) {
if (!signIn || !signUp) return null

// If the user has an account in your application, but does not yet
// have a enterprise account connected to it, you can transfer the enterprise
// account to the existing user account.
const userExistsButNeedsToSignIn =
signUp.verifications.externalAccount.status === 'transferable' &&
signUp.verifications.externalAccount.error?.code === 'external_account_exists'

if (userExistsButNeedsToSignIn) {
const res = await signIn.create({ transfer: true })

if (res.status === 'complete') {
setActive({
session: res.createdSessionId,
})
}
}

// If the user has a enterprise account but does not yet
// have an account in your app, you can create an account
// for them using the enterprise account's information.
const userNeedsToBeCreated = signIn.firstFactorVerification.status === 'transferable'

if (userNeedsToBeCreated) {
const res = await signUp.create({
transfer: true,
})

if (res.status === 'complete') {
setActive({
session: res.createdSessionId,
})
}
} else {
// If the user has an account in your application
// and has an enterprise account connected to it, you can sign them in.
signInWithEnterpriseSSO(e)
}
}

return (
<form onSubmit={(e) => handleSignIn(e)}>
<input type="email" name="email" placeholder="Enter email address" />
<button>Sign in with Enterprise SSO</button>
</form>
)
}
```
</Tab>
</Tabs>

[sign-in-redirect]: /docs/references/javascript/sign-in/authenticate-with#authenticate-with-redirect

[sign-up-redirect]: /docs/references/javascript/sign-up/authenticate-with#authenticate-with-redirect
123 changes: 4 additions & 119 deletions docs/custom-flows/oauth-connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,14 @@ description: Learn how to use the Clerk API to build a custom sign-up and sign-i

## Before you start

You must configure your application instance through the Clerk Dashboard for the social connection(s) that you want to use, as described at [the top of the OAuth guide](/docs/authentication/social-connections/oauth#configuration).
You must configure your application instance through the Clerk Dashboard for the social connection(s) that you want to use. Visit [the appropriate guide for your platform](/docs/authentication/social-connections/overview) to learn how to configure your instance.

## Create the sign-up and sign-in flow

When using OAuth, the sign-up and sign-in flows are equivalent.
<Include src="_partials/custom-flows/sso-connections" />

<Tabs items={["Next.js", "Expo", "iOS (beta)"]}>
<Tab>
A successful OAuth flow consists of the following steps:

1. Start the OAuth flow by calling [`SignIn.authenticateWithRedirect(params)`](/docs/references/javascript/sign-in/authenticate-with#authenticate-with-redirect) or [`SignUp.authenticateWithRedirect(params)`](/docs/references/javascript/sign-up/authenticate-with#authenticate-with-redirect). Both of these methods require a `redirectUrl` param, which is the URL that the browser will be redirected to once the user authenticates with the OAuth provider.
1. Create a route at the URL that the `redirectUrl` param points to. The following example names this route `/sso-callback`. This route should call the [`Clerk.handleRedirectCallback()`](/docs/references/javascript/clerk/handle-navigation#handle-redirect-callback) method or simply render the prebuilt [`<AuthenticateWithRedirectCallback/>`](/docs/components/control/authenticate-with-callback) component.

The following example shows two files:

1. The sign-in page where the user can start the OAuth flow.
1. The SSO callback page where the OAuth flow is completed.

<CodeBlockTabs options={["Sign in page", "SSO callback page"]}>
```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client'
Expand Down Expand Up @@ -70,8 +60,8 @@ When using OAuth, the sign-up and sign-in flows are equivalent.
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs'

export default function SSOCallback() {
// Handle the redirect flow by rendering the
// prebuilt AuthenticateWithRedirectCallback component.
// Handle the redirect flow by calling the Clerk.handleRedirectCallback() method
// or rendering the prebuilt <AuthenticateWithRedirectCallback/> component.
// This is the final step in the custom OAuth flow.
return <AuthenticateWithRedirectCallback />
}
Expand Down Expand Up @@ -148,108 +138,3 @@ When using OAuth, the sign-up and sign-in flows are equivalent.
```
</Tab>
</Tabs>

## OAuth account transfer flows

It is common for users who are authenticating with OAuth to use a sign-in button when they mean to sign-up, and vice versa. For those cases, the `SignIn` and `SignUp` objects have a `transferable` status that indicates whether the user can be transferred to the other flow.

**If you would like to keep your sign-in and sign-up flows completely separate, then you can skip this section.**

The following example demonstrates how to handle these cases in your sign-in flow. To apply the same logic to the sign-up flow, simply replace `signIn.authenticateWithRedirect()` with `signUp.authenticateWithRedirect()` in your code.

<Tabs items={["Next.js", "iOS (beta)"]}>
<Tab>
```tsx {{ filename: 'app/sign-in/page.tsx', collapsible: true }}
'use client'

import * as React from 'react'
import { OAuthStrategy } from '@clerk/types'
import { useSignIn, useSignUp } from '@clerk/nextjs'

export default function OauthSignIn() {
const { signIn } = useSignIn()
const { signUp, setActive } = useSignUp()

if (!signIn || !signUp) return null

const signInWith = (strategy: OAuthStrategy) => {
return signIn.authenticateWithRedirect({
strategy,
redirectUrl: '/sign-up/sso-callback',
redirectUrlComplete: '/',
})
}

async function handleSignIn(strategy: OAuthStrategy) {
if (!signIn || !signUp) return null

// If the user has an account in your application, but does not yet
// have an OAuth account connected to it, you can transfer the OAuth
// account to the existing user account.
const userExistsButNeedsToSignIn =
signUp.verifications.externalAccount.status === 'transferable' &&
signUp.verifications.externalAccount.error?.code === 'external_account_exists'

if (userExistsButNeedsToSignIn) {
const res = await signIn.create({ transfer: true })

if (res.status === 'complete') {
setActive({
session: res.createdSessionId,
})
}
}

// If the user has an OAuth account but does not yet
// have an account in your app, you can create an account
// for them using the OAuth information.
const userNeedsToBeCreated = signIn.firstFactorVerification.status === 'transferable'

if (userNeedsToBeCreated) {
const res = await signUp.create({
transfer: true,
})

if (res.status === 'complete') {
setActive({
session: res.createdSessionId,
})
}
} else {
// If the user has an account in your application
// and has an OAuth account connected to it, you can sign them in.
signInWith(strategy)
}
}

// Render a button for each supported OAuth provider
// you want to add to your app. This example uses only Google.
return (
<div>
<button onClick={() => handleSignIn('oauth_google')}>Sign in with Google</button>
</div>
)
}
```
</Tab>

<Tab>
```swift {{ filename: 'OAuthView.swift', collapsible: true }}
// If a user attempted to sign in but doesn't have an account,
// Clerk will try to handle the transfer to sign up for you.
// If that's not possible (i.e. maybe signing up requires a CAPTCHA Token),
// authenticateWithRedirect() will return the original sign in to you
// so you can manually perform the transfer to sign up.

// Check if the sign in needs to be transferred to a sign up
if let signIn = externalAuthResult?.signIn,
signIn.firstFactorVerification?.status == .transferable {
// Create a new sign up with the strategy `.transfer`
let signUp = try await SignUp.create(
strategy: .transfer,
captchaToken: "TOKEN"
)
}
```
</Tab>
</Tabs>

0 comments on commit fe5f63f

Please sign in to comment.