Skip to content

Commit 58a6025

Browse files
feat: add Native to Web SSO example and fix iOS implementation (#1409)
1 parent d09ed22 commit 58a6025

File tree

6 files changed

+150
-16
lines changed

6 files changed

+150
-16
lines changed

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PODS:
2-
- A0Auth0 (5.1.0):
2+
- A0Auth0 (5.2.1):
33
- Auth0 (= 2.14)
44
- boost
55
- DoubleConversion
@@ -2778,7 +2778,7 @@ EXTERNAL SOURCES:
27782778
:path: "../node_modules/react-native/ReactCommon/yoga"
27792779

27802780
SPEC CHECKSUMS:
2781-
A0Auth0: 022a85f2093860dcbf9e5337946f6cda5780de4d
2781+
A0Auth0: 9253099fae9372f663f89abbf5d02a6b36faf45c
27822782
Auth0: 022dda235af8a664a4faf9e7b60b063b5bc08373
27832783
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
27842784
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb

example/src/screens/class-based/ClassLogin.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Button from '../../components/Button';
99
import Header from '../../components/Header';
1010
import Result from '../../components/Result';
1111
import type { ClassDemoStackParamList } from '../../navigation/ClassDemoNavigator';
12+
import config from '../../auth0-configuration';
1213

1314
type NavigationProp = StackNavigationProp<
1415
ClassDemoStackParamList,
@@ -24,7 +25,10 @@ const ClassLoginScreen = () => {
2425
setLoading(true);
2526
setError(null);
2627
try {
27-
const credentials = await auth0.webAuth.authorize();
28+
const credentials = await auth0.webAuth.authorize({
29+
scope: 'openid profile email offline_access',
30+
audience: `https://${config.domain}/api/v2/`,
31+
});
2832
// On success, we save the credentials and navigate to the profile screen.
2933
await auth0.credentialsManager.saveCredentials(credentials);
3034
navigation.replace('ClassProfile', { credentials });

example/src/screens/class-based/ClassProfile.tsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
StyleSheet,
77
Text,
88
Alert,
9+
Linking,
910
} from 'react-native';
1011
import { RouteProp, NavigationProp } from '@react-navigation/native';
1112
import { jwtDecode } from 'jwt-decode';
@@ -16,7 +17,6 @@ import UserInfo from '../../components/UserInfo';
1617
import { User, Credentials, ApiCredentials } from 'react-native-auth0';
1718
import type { ClassDemoStackParamList } from '../../navigation/ClassDemoNavigator';
1819
import LabeledInput from '../../components/LabeledInput';
19-
import config from '../../auth0-configuration';
2020
import Result from '../../components/Result';
2121

2222
type ProfileRouteProp = RouteProp<ClassDemoStackParamList, 'ClassProfile'>;
@@ -31,6 +31,7 @@ interface State {
3131
result: Credentials | ApiCredentials | object | boolean | null;
3232
error: Error | null;
3333
audience: string;
34+
webAppUrl: string;
3435
}
3536

3637
class ClassProfileScreen extends Component<Props, State> {
@@ -41,7 +42,8 @@ class ClassProfileScreen extends Component<Props, State> {
4142
user,
4243
result: null,
4344
error: null,
44-
audience: config.audience,
45+
audience: '',
46+
webAppUrl: 'https://your-web-app.com/login',
4547
};
4648
}
4749

@@ -74,7 +76,7 @@ class ClassProfileScreen extends Component<Props, State> {
7476
};
7577

7678
render() {
77-
const { user, result, error, audience } = this.state;
79+
const { user, result, error, audience, webAppUrl } = this.state;
7880
const { accessToken } = this.props.route.params.credentials;
7981

8082
return (
@@ -143,6 +145,65 @@ class ClassProfileScreen extends Component<Props, State> {
143145
/>
144146
</Section>
145147

148+
<Section title="Native to Web SSO (Early Access)">
149+
<Text style={styles.description}>
150+
Exchange your refresh token for a Session Transfer Token to enable
151+
seamless SSO to your web application.
152+
</Text>
153+
<LabeledInput
154+
label="Web App URL"
155+
value={webAppUrl}
156+
onChangeText={(text) => this.setState({ webAppUrl: text })}
157+
autoCapitalize="none"
158+
placeholder="https://your-web-app.com/login"
159+
/>
160+
<Button
161+
onPress={() =>
162+
this.runTest(
163+
() => auth0.credentialsManager.getSSOCredentials(),
164+
'Get SSO Credentials'
165+
)
166+
}
167+
title="credentialsManager.getSSOCredentials()"
168+
/>
169+
<Button
170+
onPress={async () => {
171+
try {
172+
this.setState({ error: null });
173+
const ssoCredentials =
174+
await auth0.credentialsManager.getSSOCredentials();
175+
this.setState({ result: ssoCredentials });
176+
177+
// Open web app with session transfer token
178+
const url = `${webAppUrl}?session_transfer_token=${ssoCredentials.sessionTransferToken}`;
179+
180+
Alert.alert(
181+
'Open Web App',
182+
`Open ${webAppUrl} with session transfer token?`,
183+
[
184+
{ text: 'Cancel', style: 'cancel' },
185+
{
186+
text: 'Open',
187+
onPress: async () => {
188+
const supported = await Linking.canOpenURL(url);
189+
if (supported) {
190+
await Linking.openURL(url);
191+
} else {
192+
Alert.alert('Error', `Cannot open URL: ${url}`);
193+
}
194+
},
195+
},
196+
]
197+
);
198+
} catch (e) {
199+
this.setState({ error: e as Error });
200+
}
201+
}}
202+
title="Get SSO Credentials & Open Web App"
203+
style={styles.primaryButton}
204+
/>
205+
</Section>
206+
146207
<Section title="Navigation & Logout">
147208
<Button
148209
onPress={() =>
@@ -188,8 +249,10 @@ const styles = StyleSheet.create({
188249
},
189250
sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
190251
buttonGroup: { gap: 10 },
252+
description: { fontSize: 14, color: '#757575', marginBottom: 10 },
191253
destructiveButton: { backgroundColor: '#424242' },
192254
secondaryButton: { backgroundColor: '#FF9800' },
255+
primaryButton: { backgroundColor: '#4CAF50' },
193256
});
194257

195258
export default ClassProfileScreen;

example/src/screens/hooks/CredentialsScreen.tsx

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import React, { useState } from 'react';
2-
import { SafeAreaView, ScrollView, StyleSheet, View, Text } from 'react-native';
2+
import {
3+
SafeAreaView,
4+
ScrollView,
5+
StyleSheet,
6+
View,
7+
Text,
8+
Linking,
9+
Alert,
10+
} from 'react-native';
311
import { useAuth0, Credentials, ApiCredentials } from 'react-native-auth0';
412
import Button from '../../components/Button';
513
import Header from '../../components/Header';
614
import Result from '../../components/Result';
715
import LabeledInput from '../../components/LabeledInput';
8-
import config from '../../auth0-configuration';
916

1017
const CredentialsScreen = () => {
1118
const {
@@ -15,14 +22,16 @@ const CredentialsScreen = () => {
1522
getApiCredentials,
1623
clearApiCredentials,
1724
revokeRefreshToken,
25+
getSSOCredentials,
1826
} = useAuth0();
1927

2028
const [result, setResult] = useState<
2129
Credentials | ApiCredentials | object | boolean | null
2230
>(null);
2331
const [error, setError] = useState<Error | null>(null);
24-
const [audience, setAudience] = useState(config.audience);
32+
const [audience, setAudience] = useState('');
2533
const [scope, setScope] = useState('openid profile email');
34+
const [webAppUrl, setWebAppUrl] = useState('https://your-web-app.com/login');
2635

2736
const runTest = async (testFn: () => Promise<any>, title: string) => {
2837
setError(null);
@@ -117,6 +126,59 @@ const CredentialsScreen = () => {
117126
style={styles.secondaryButton}
118127
/>
119128
</Section>
129+
130+
<Section title="Native to Web SSO (Early Access)">
131+
<Text style={styles.description}>
132+
Exchange your refresh token for a Session Transfer Token to enable
133+
seamless SSO to your web application.
134+
</Text>
135+
<LabeledInput
136+
label="Web App URL"
137+
value={webAppUrl}
138+
onChangeText={setWebAppUrl}
139+
autoCapitalize="none"
140+
placeholder="https://your-web-app.com/login"
141+
/>
142+
<Button
143+
onPress={() => runTest(getSSOCredentials, 'Get SSO Credentials')}
144+
title="getSSOCredentials()"
145+
/>
146+
<Button
147+
onPress={async () => {
148+
try {
149+
setError(null);
150+
const ssoCredentials = await getSSOCredentials();
151+
setResult(ssoCredentials);
152+
153+
// Open web app with session transfer token
154+
const url = `${webAppUrl}?session_transfer_token=${ssoCredentials.sessionTransferToken}`;
155+
156+
Alert.alert(
157+
'Open Web App',
158+
`Open ${webAppUrl} with session transfer token?`,
159+
[
160+
{ text: 'Cancel', style: 'cancel' },
161+
{
162+
text: 'Open',
163+
onPress: async () => {
164+
const supported = await Linking.canOpenURL(url);
165+
if (supported) {
166+
await Linking.openURL(url);
167+
} else {
168+
Alert.alert('Error', `Cannot open URL: ${url}`);
169+
}
170+
},
171+
},
172+
]
173+
);
174+
} catch (e) {
175+
setError(e as Error);
176+
}
177+
}}
178+
title="Get SSO Credentials & Open Web App"
179+
style={styles.primaryButton}
180+
/>
181+
</Section>
120182
</ScrollView>
121183
</SafeAreaView>
122184
);
@@ -147,8 +209,10 @@ const styles = StyleSheet.create({
147209
},
148210
sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
149211
buttonGroup: { gap: 10 },
212+
description: { fontSize: 14, color: '#757575', marginBottom: 10 },
150213
destructiveButton: { backgroundColor: '#424242' },
151214
secondaryButton: { backgroundColor: '#FF9800' },
215+
primaryButton: { backgroundColor: '#4CAF50' },
152216
});
153217

154218
export default CredentialsScreen;

example/src/screens/hooks/Home.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Button from '../../components/Button';
1212
import Header from '../../components/Header';
1313
import LabeledInput from '../../components/LabeledInput';
1414
import Result from '../../components/Result';
15+
import config from '../../auth0-configuration';
1516

1617
const HomeScreen = () => {
1718
const {
@@ -30,7 +31,10 @@ const HomeScreen = () => {
3031

3132
const onLogin = async () => {
3233
try {
33-
await authorize();
34+
await authorize({
35+
scope: 'openid profile email offline_access',
36+
audience: `https://${config.domain}/api/v2/`,
37+
});
3438
} catch (e) {
3539
console.log('Login error: ', e);
3640
}

ios/NativeBridge.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,19 +244,18 @@ public class NativeBridge: NSObject {
244244
}
245245

246246
@objc public func getSSOCredentials(parameters: [String: Any], headers: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
247-
credentialsManager.ssoCredentials(parameters: parameters, headers: headers) { result in
247+
let stringHeaders = headers.compactMapValues { $0 as? String }
248+
credentialsManager.ssoCredentials(parameters: parameters, headers: stringHeaders) { result in
248249
switch result {
249250
case .success(let ssoCredentials):
250251
var response: [String: Any] = [
251252
"sessionTransferToken": ssoCredentials.sessionTransferToken,
252-
"tokenType": ssoCredentials.tokenType,
253-
"expiresIn": ssoCredentials.expiresIn
253+
"tokenType": ssoCredentials.issuedTokenType,
254+
"expiresIn": ssoCredentials.expiresIn,
255+
"idToken": ssoCredentials.idToken
254256
]
255257

256258
// Add optional fields if present
257-
if let idToken = ssoCredentials.idToken {
258-
response["idToken"] = idToken
259-
}
260259
if let refreshToken = ssoCredentials.refreshToken {
261260
response["refreshToken"] = refreshToken
262261
}

0 commit comments

Comments
 (0)