Skip to content

Commit bd96d6c

Browse files
feat(clerk-expo): Introduce SAML support (#4880)
Co-authored-by: Nicolas Lopes <[email protected]>
1 parent a26cf0f commit bd96d6c

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed

.changeset/selfish-worms-switch.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/clerk-expo': minor
3+
---
4+
5+
Introduce support for SSO with SAML
6+
7+
- Introduce `useSSO` hook to support a wider range of SSO flow types
8+
- Deprecate `useOAuth` in favor of new `useSSO` hook

packages/expo/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export {
1010
useUser,
1111
} from '@clerk/clerk-react';
1212

13+
export * from './useSSO';
1314
export * from './useOAuth';
1415
export * from './useAuth';

packages/expo/src/hooks/useOAuth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export type StartOAuthFlowReturnType = {
2424
authSessionResult?: WebBrowser.WebBrowserAuthSessionResult;
2525
};
2626

27+
/**
28+
* @deprecated Use `useSSO` instead
29+
*/
2730
export function useOAuth(useOAuthParams: UseOAuthFlowParams) {
2831
const { strategy } = useOAuthParams || {};
2932
if (!strategy) {

packages/expo/src/hooks/useSSO.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useSignIn, useSignUp } from '@clerk/clerk-react';
2+
import type { EnterpriseSSOStrategy, OAuthStrategy, SetActive, SignInResource, SignUpResource } from '@clerk/types';
3+
import * as AuthSession from 'expo-auth-session';
4+
import * as WebBrowser from 'expo-web-browser';
5+
6+
import { errorThrower } from '../utils/errors';
7+
8+
export type StartSSOFlowParams = {
9+
unsafeMetadata?: SignUpUnsafeMetadata;
10+
} & (
11+
| {
12+
strategy: OAuthStrategy;
13+
}
14+
| {
15+
strategy: EnterpriseSSOStrategy;
16+
identifier: string;
17+
}
18+
);
19+
20+
export type StartSSOFlowReturnType = {
21+
createdSessionId: string | null;
22+
authSessionResult?: WebBrowser.WebBrowserAuthSessionResult;
23+
setActive?: SetActive;
24+
signIn?: SignInResource;
25+
signUp?: SignUpResource;
26+
};
27+
28+
export function useSSO() {
29+
const { signIn, setActive, isLoaded: isSignInLoaded } = useSignIn();
30+
const { signUp, isLoaded: isSignUpLoaded } = useSignUp();
31+
32+
async function startSSOFlow(startSSOFlowParams: StartSSOFlowParams): Promise<StartSSOFlowReturnType> {
33+
if (!isSignInLoaded || !isSignUpLoaded) {
34+
return {
35+
createdSessionId: null,
36+
signIn,
37+
signUp,
38+
setActive,
39+
};
40+
}
41+
42+
const { strategy, unsafeMetadata } = startSSOFlowParams ?? {};
43+
44+
/**
45+
* Creates a redirect URL based on the application platform
46+
* It must be whitelisted, either via Clerk Dashboard, or BAPI, in order
47+
* to include the `rotating_token_nonce` on SSO callback
48+
* @ref https://clerk.com/docs/reference/backend-api/tag/Redirect-URLs#operation/CreateRedirectURL
49+
*/
50+
const redirectUrl = AuthSession.makeRedirectUri({
51+
path: 'sso-callback',
52+
});
53+
54+
await signIn.create({
55+
strategy,
56+
redirectUrl,
57+
...(startSSOFlowParams.strategy === 'enterprise_sso' ? { identifier: startSSOFlowParams.identifier } : {}),
58+
});
59+
60+
const { externalVerificationRedirectURL } = signIn.firstFactorVerification;
61+
if (!externalVerificationRedirectURL) {
62+
return errorThrower.throw('Missing external verification redirect URL for SSO flow');
63+
}
64+
65+
const authSessionResult = await WebBrowser.openAuthSessionAsync(externalVerificationRedirectURL.toString());
66+
if (authSessionResult.type !== 'success' || !authSessionResult.url) {
67+
return {
68+
createdSessionId: null,
69+
setActive,
70+
signIn,
71+
signUp,
72+
};
73+
}
74+
75+
const params = new URL(authSessionResult.url).searchParams;
76+
const rotatingTokenNonce = params.get('rotating_token_nonce') ?? '';
77+
await signIn.reload({ rotatingTokenNonce });
78+
79+
const userNeedsToBeCreated = signIn.firstFactorVerification.status === 'transferable';
80+
if (userNeedsToBeCreated) {
81+
await signUp.create({
82+
transfer: true,
83+
unsafeMetadata,
84+
});
85+
}
86+
87+
return {
88+
createdSessionId: signUp.createdSessionId ?? signIn.createdSessionId,
89+
setActive,
90+
signIn,
91+
signUp,
92+
};
93+
}
94+
95+
return {
96+
startSSOFlow,
97+
};
98+
}

0 commit comments

Comments
 (0)