Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(clerk-expo): Deprecate useOAuth and introduce docs for useSSO #1908

Merged
merged 27 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2e34970
Add bare draft for `useSSO` docs
LauraBeatris Jan 16, 2025
1e71a1a
Update snippets
LauraBeatris Jan 16, 2025
69c704d
update
victoriaxyz Jan 16, 2025
7f52af6
fix intros to code examples
victoriaxyz Jan 16, 2025
bcb5f9c
fix: Rollback guides for deprecated `useOAuth`
LauraBeatris Jan 17, 2025
2a15e4e
chore: Add deprecation warning
LauraBeatris Jan 17, 2025
bc2e51c
chore: Remove `redirectUrl` from properties
LauraBeatris Jan 17, 2025
bb12f81
chore: Improve description for `strategy` property
LauraBeatris Jan 17, 2025
ba4aed0
chore: Fix `useSSO` link
LauraBeatris Jan 17, 2025
36f8ba9
docs review
alexisintech Jan 17, 2025
11600ff
update oauth connections custom flow guide
alexisintech Jan 17, 2025
5c6d895
Update strategies types from OAuth-specific to general SSO (#1907)
LauraBeatris Jan 17, 2025
ba8abea
fix broken links
alexisintech Jan 17, 2025
cad401c
fix links
alexisintech Jan 21, 2025
6d51c33
Update `useSSO` with latest interface
LauraBeatris Jan 24, 2025
7187a29
Update deep link path for SSO callback
LauraBeatris Jan 24, 2025
17b642c
Update Expo overview to mention `useSSO`
LauraBeatris Jan 24, 2025
80081e5
Update snippets
LauraBeatris Jan 24, 2025
81107e2
Rollback OAuth connections changes
LauraBeatris Jan 24, 2025
d4c8941
Merge branch 'main' into laura/orgs-471-update-docs-for-clerkexpo
alexisintech Jan 30, 2025
0a5b335
docs review
alexisintech Jan 30, 2025
7d3fd2f
remove whitelist info from usesso doc
alexisintech Jan 31, 2025
01eef70
add callout; update todo
alexisintech Feb 4, 2025
1fff4b9
final docs review :fingerscrossed:
alexisintech Feb 4, 2025
0f6e969
Merge branch 'main' into laura/orgs-471-update-docs-for-clerkexpo
alexisintech Feb 4, 2025
5a8a7f9
Add reference for `authSessionResult`
LauraBeatris Feb 4, 2025
9c269f1
Fix linting and formatting
LauraBeatris Feb 4, 2025
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
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.
78 changes: 78 additions & 0 deletions docs/_partials/expo/enterprise-sso-custom-flow.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
> [!IMPORTANT]
> 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 **will both sign up _and_ sign in users**, eliminating the need for a separate sign-up page.

The following example:

1. Accesses the `startSSOFlow()` method using the [`useSSO()`](/docs/references/expo/use-sso) hook.
1. Calls the `startSSOFlow()` method with the `strategy` param set to `enterprise_sso` and the `identifier` param set to the user's email address that they provided.
- If authentication is successful, the `setActive()` method is called to set the active session with the new `createdSessionId`.
- If authentication is not successful, you can handle the missing requirements, such as MFA, using the [`signIn`](/docs/references/javascript/sign-in/sign-in) or [`signUp`](/docs/references/javascript/sign-up/sign-up) object returned from `startSSOFlow()`, depending on if the user is signing in or signing up. These objects include properties, like `status`, that can be used to determine the next steps. See the respective linked references for more information.

```tsx {{ filename: 'app/(auth)/sign-in.tsx', collapsible: true }}
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'

export const useWarmUpBrowser = () => {
useEffect(() => {
// Preloads the browser for Android devices to reduce authentication load time
// See: https://docs.expo.dev/guides/authentication/#improving-user-experience
void WebBrowser.warmUpAsync()
return () => {
// Cleanup: closes browser when component unmounts
void WebBrowser.coolDownAsync()
}
}, [])
}

// Handle any pending authentication sessions
WebBrowser.maybeCompleteAuthSession()

export default function Page() {
useWarmUpBrowser()

const [email, setEmail] = useState('')

// Use the `useSSO()` hook to access the `startSSOFlow()` method
const { startSSOFlow } = useSSO()

const onPress = async () => {
try {
// Start the authentication process by calling `startSSOFlow()`
const { createdSessionId, setActive, signIn, signUp } = await startSSOFlow({
strategy: 'enterprise_sso',
identifier: email,
})

// If sign in was successful, set the active session
if (createdSessionId) {
setActive!({ session: createdSessionId })
} else {
// If there is no `createdSessionId`,
// there are missing requirements, such as MFA
// Use the `signIn` or `signUp` returned from `startSSOFlow`
// to handle next steps
}
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}

return (
<View>
<TextInput
value={email}
onChangeText={setEmail}
placeholder="Enter email"
placeholderTextColor="#666666"
/>
<Button title="Sign in with SAML" onPress={onPress} />
</View>
)
}
```
71 changes: 40 additions & 31 deletions docs/_partials/expo/oauth-custom-flow.mdx
LauraBeatris marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,58 +1,67 @@
The following example demonstrates how to create a custom OAuth sign-in flow for [Google accounts](/docs/authentication/social-connections/google).
The following example **will both sign up _and_ sign in users**, eliminating the need for a separate sign-up page.

The following example:

1. Accesses the `startSSOFlow()` method using the [`useSSO()`](/docs/references/expo/use-sso) hook.
1. Calls the `startSSOFlow()` method with the `strategy` param set to `oauth_google`, but you can use any of the [supported OAuth strategies](/docs/references/javascript/types/sso#oauth-strategy).
- If authentication is successful, the `setActive()` method is called to set the active session with the new `createdSessionId`.
- If authentication is not successful, you can handle the missing requirements, such as MFA, using the [`signIn`](/docs/references/javascript/sign-in/sign-in) or [`signUp`](/docs/references/javascript/sign-up/sign-up) object returned from `startSSOFlow()`, depending on if the user is signing in or signing up. These objects include properties, like `status`, that can be used to determine the next steps. See the respective linked references for more information.

```tsx {{ filename: 'app/(auth)/sign-in.tsx', collapsible: true }}
import React from 'react'
import * as WebBrowser from 'expo-web-browser'
import { Text, View, Button } from 'react-native'
import { Link } from 'expo-router'
import { useOAuth } from '@clerk/clerk-expo'
import * as Linking from 'expo-linking'
import React, { useCallback, useEffect } from "react";
import * as WebBrowser from "expo-web-browser";
import { useSSO } from "@clerk/clerk-expo";
import { View, Button } from "react-native";

export const useWarmUpBrowser = () => {
React.useEffect(() => {
// Warm up the android browser to improve UX
// https://docs.expo.dev/guides/authentication/#improving-user-experience
void WebBrowser.warmUpAsync()
useEffect(() => {
// Preloads the browser for Android devices to reduce authentication load time
// See: https://docs.expo.dev/guides/authentication/#improving-user-experience
void WebBrowser.warmUpAsync();
return () => {
void WebBrowser.coolDownAsync()
}
}, [])
}
// Cleanup: closes browser when component unmounts
void WebBrowser.coolDownAsync();
};
}, []);
};

WebBrowser.maybeCompleteAuthSession()
// Handle any pending authentication sessions
WebBrowser.maybeCompleteAuthSession();

export default function Page() {
useWarmUpBrowser()
useWarmUpBrowser();

const { startOAuthFlow } = useOAuth({ strategy: 'oauth_google' })
// Use the `useSSO()` hook to access the `startSSOFlow()` method
const { startSSOFlow } = useSSO();

const onPress = React.useCallback(async () => {
const onPress = useCallback(async () => {
try {
const { createdSessionId, signIn, signUp, setActive } = await startOAuthFlow({
redirectUrl: Linking.createURL('/dashboard', { scheme: 'myapp' }),
})
// Start the authentication process by calling `startSSOFlow()`
const { createdSessionId, setActive, signIn, signUp } =
await startSSOFlow({
strategy: "oauth_google",
});

// If sign in was successful, set the active session
if (createdSessionId) {
setActive!({ session: createdSessionId })
setActive!({ session: createdSessionId });
} else {
// Use signIn or signUp returned from startOAuthFlow
// for next steps, such as MFA
// If there is no `createdSessionId`,
// there are missing requirements, such as MFA
// Use the `signIn` or `signUp` returned from `startSSOFlow`
// to handle next steps
}
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
console.error(JSON.stringify(err, null, 2));
}
}, [])
}, []);

return (
<View>
<Link href="/">
<Text>Home</Text>
</Link>
<Button title="Sign in with Google" onPress={onPress} />
</View>
)
);
}
```
133 changes: 15 additions & 118 deletions docs/custom-flows/enterprise-connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,171 +11,68 @@ 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-in and sign-up flows are equivalent. A successful Enterprise SSO flow consists of the following steps:

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.

<Tabs items={["Next.js"]}>
<Tabs items={["Next.js", "Expo"]}>
<Tab>
<Include src="_partials/custom-flows/sso-connections" />

<CodeBlockTabs options={["Sign in page", "SSO callback page"]}>
```tsx {{ filename: 'app/sign-in/page.tsx' }}
'use client'

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

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

if (!signIn || !signUp) return null
const { signIn, isLoaded } = useSignIn()

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

if (!isLoaded) return null

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

signIn
.authenticateWithRedirect({
identifier: email,
strategy: 'enterprise_sso',
redirectUrl: '/sign-up/sso-callback',
redirectUrl: '/sign-in/sso-callback',
redirectUrlComplete: '/',
})
.then((res) => {
console.log(res)
})
.catch((err: any) => {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.log(err.errors)
console.error(err, null, 2)
})
}

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

```jsx {{ filename: 'app/sign-up/sso-callback/page.tsx' }}
```jsx {{ filename: 'app/sign-in/sso-callback/page.tsx' }}
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 />
}
```
</CodeBlockTabs>
</Tab>
</Tabs>

## Enterprise account transfer flows

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/[[...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-up/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>
)
}
```
<Include src="_partials/expo/enterprise-sso-custom-flow" />
</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
Loading