[英]Okta authentication, how to use the response.params?
We are trying to use Expo authentication with Okta as stated here:我们正在尝试使用 Okta 的 Expo 身份验证,如下所述:
https://docs.expo.dev/guides/authentication/#okta https://docs.expo.dev/guides/authentication/#okta
Expo has very good documentation for lot's of stuff, but for the Okta authentication unfortunately we could not sort out how to use the library in a correct way. Expo 有很多很好的文档,但不幸的是,对于 Okta 身份验证,我们无法弄清楚如何以正确的方式使用该库。
Currently, with lot's of suffering (mostly because of the ambiguity in Okta's configuration pages), we came to a certain point where the following code correctly responds the code
parameter.目前,我们遇到了很多麻烦(主要是因为 Okta 配置页面中的歧义),我们来到了一个特定的点,下面的代码正确地响应了code
参数。 This is the exact same part from Expo documentation:这与 Expo 文档中的部分完全相同:
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
But unfortunately we could not find any method how we can use the parameter code
to get the scope information, email, name, etc...但不幸的是,我们找不到任何方法可以使用参数code
来获取范围信息、电子邮件、姓名等...
Can anybody guide us how we can use the object code
to retrieve these data?谁能指导我们如何使用目标code
来检索这些数据? (The Okta documentation is not clear for this either, so we are stuck.) (Okta 文档对此也不清楚,所以我们被卡住了。)
Edit 1:编辑 1:
The response
has the following structure: response
具有以下结构:
response: {
"type": "success",
"error": null,
"url": "http://localhost:19006/?code=fUMjE4kBX2QZXXXXXX_XXXXXXXMQ084kEPrTqDa9FTs&state=3XXXXXXXXz",
"params": {
"code": "fUMjE4kBX2QZXXXXXX_XXXXXXXMQ084kEPrTqDa9FTs",
"state": "3XXXXXXXXz"
},
"authentication": null,
"errorCode": null
}
Edit 2:编辑 2:
Calling exchangeCodeAsync
also yields errors.调用exchangeCodeAsync
也会产生错误。
Code:代码:
const tokenRequestParams = {
code: code,
clientId: config.okta.clientId,
redirectUri: oktaRedirectUri,
extraParams: {
code_verifier: authRequest.codeVerifier
},
}
const tokenResult = await exchangeCodeAsync(tokenRequestParams, discovery);
Error:错误:
TokenRequest.ts:205 Uncaught (in promise) Error: Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the client attempted to authenticate via the "Authorization" request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and include the "WWW-Authenticate" response header field matching the authentication scheme used by the client.
More info: Client authentication failed. Either the client or the client credentials are invalid.
at AccessTokenRequest.<anonymous> (TokenRequest.ts:205:1)
at Generator.next (<anonymous>)
at asyncGeneratorStep (asyncToGenerator.js:3:1)
at _next (asyncToGenerator.js:22:1)
PS: I asked the same question to the Expo forums also here . PS:我也在世博论坛问了同样的问题。 If we can solve there, I plan to reflect to here also for wider audience.如果我们能在那里解决,我打算在这里也为更广泛的观众反映。 (The method can be related with Okta rather than Expo itself.) (该方法可能与 Okta 有关,而不是 Expo 本身。)
there are two ways to use Okta in React Native在 React Native 中有两种使用 Okta 的方法
1. By Restful APIs, using fetch/Axios 1. 通过 Restful API,使用 fetch/Axios
2. By Using Native SDK 2. 使用原生SDK
here is the full code of okta using restful 这是使用 restful 的 okta 的完整代码
import React, { useState } from "react";
import {
ScrollView,
StyleSheet,
Text,
View,
TouchableOpacity,
Platform,
} from "react-native";
import {
useAutoDiscovery,
useAuthRequest,
makeRedirectUri,
exchangeCodeAsync,
} from "expo-auth-session";
import { maybeCompleteAuthSession } from "expo-web-browser";
import axios from "axios";
const oktaConfig = {
okta_issuer_url: "",
okta_client_id: "",
okta_callback_url: "com.okta.<OKTA_DOMAIN>:/callback",
};
export default App = (props) => {
const useProxy = true;
if (Platform.OS === "web") {
maybeCompleteAuthSession();
}
const discovery = useAutoDiscovery(oktaConfig.okta_issuer_url);
// When promptAsync is invoked we will get back an Auth Code
// This code can be exchanged for an Access/ID token as well as
// User Info by making calls to the respective endpoints
const [authRequest, response, promptAsync] = useAuthRequest(
{
clientId: oktaConfig.okta_client_id,
scopes: ["openid", "profile"],
redirectUri: makeRedirectUri({
native: oktaConfig.okta_callback_url,
useProxy,
}),
},
discovery
);
async function oktaCognitoLogin() {
const loginResult = await promptAsync({ useProxy });
ExchangeForToken(loginResult, authRequest, discovery);
}
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.equalSizeButtons}
onPress={() => oktaCognitoLogin()}
>
<Text style={styles.buttonText}>Okta Login</Text>
</TouchableOpacity>
</View>
<ScrollView>
{response && <Text>{JSON.stringify(response, null, 2)}</Text>}
</ScrollView>
</View>
);
};
this is how, we can get exchange token and then get user info by using restful api就是这样,我们可以获取交换令牌,然后使用 restful api 获取用户信息
//After getting the Auth Code we need to exchange it for credentials
async function ExchangeForToken(response, authRequest, discovery) {
// React hooks must be used within functions
const useProxy = true;
const expoRedirectURI = makeRedirectUri({
native: oktaConfig.okta_callback_url,
useProxy,
})
const tokenRequestParams = {
code: response.params.code,
clientId: oktaConfig.okta_client_id,
redirectUri: expoRedirectURI,
extraParams: {
code_verifier: authRequest.codeVerifier
},
}
const tokenResult = await exchangeCodeAsync(
tokenRequestParams,
discovery
)
const creds = ExchangeForUser(tokenResult)
const finalAuthResult = {
token_res : tokenResult,
user_creds : creds
}
console.log("Final Result: ", finalAuthResult)
}
this is how we can get user info by using restful api这就是我们如何使用 restful api 获取用户信息
async function ExchangeForUser(tokenResult) {
const accessToken = tokenResult.accessToken;
const idToken = tokenResult.idToken;
//make an HTTP direct call to the Okta User Info endpoint of our domain
const usersRequest = `${oktaConfig.okta_issuer_url}/v1/userinfo`
const userPromise = await axios.get(usersRequest, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
console.log(userPromise, "user Info");
}
const styles = StyleSheet.create({
container: {
margin: 10,
marginTop: 20,
},
buttonContainer: {
flexDirection: "row",
alignItems: "center",
margin: 5,
},
equalSizeButtons: {
width: "50%",
backgroundColor: "#023788",
borderColor: "#6df1d8",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 9,
borderWidth: 1,
shadowColor: "#6df1d8",
shadowOpacity: 8,
shadowRadius: 3,
shadowOffset: {
height: 0,
width: 0,
},
},
buttonText: {
color: "#ffffff",
fontSize: 16,
},
});
for native SDK, you can use okta-react-native package like this对于本机 SDK,您可以像这样使用okta-react-native包
Login Screen登录屏幕
import React from 'react';
import {
Alert,
Button,
StyleSheet,
TextInput,
View,
ActivityIndicator
} from 'react-native';
import {
signIn,
introspectIdToken
} from '@okta/okta-react-native';
export default class CustomLogin extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
username: '',
password: '',
};
}
async componentDidMount() {
}
signInCustom = () => {
this.setState({ isLoading: true });
signIn({ username: this.state.username, password: this.state.password })
.then(() => {
introspectIdToken()
.then(idToken => {
this.props.navigation.navigate('ProfilePage', { idToken: idToken, isBrowserScenario: false });
}).finally(() => {
this.setState({
isLoading: false,
username: '',
password: '',
});
});
})
.catch(error => {
// For some reason the app crashes when only one button exist (only with loaded bundle, debug is OK) 🤦♂️
Alert.alert(
"Error",
error.message,
[
{
text: "Cancel",
onPress: () => console.log("Cancel Pressed"),
style: "cancel"
},
{ text: "OK", onPress: () => console.log("OK Pressed") }
]
);
this.setState({
isLoading: false
});
});
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder='Username'
onChangeText={input => this.setState({ username: input })}
testID="username_input"
/>
<TextInput
style={styles.input}
placeholder='Password'
onChangeText={input => this.setState({ password: input })}
testID="password_input"
/>
<Button
onPress={this.signInCustom}
title="Sign in"
testID='sign_in_button'
/>
<View style={styles.flexible}></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
input: {
height: 40,
width: '80%',
margin: 12,
borderWidth: 1,
padding: 10,
},
flexible: {
flex: 1,
}
});
Profile Screen配置文件屏幕
import React from 'react';
import {
Text,
Button,
StyleSheet,
TextInput,
View,
} from 'react-native';
import {
signOut,
revokeAccessToken,
revokeIdToken,
clearTokens,
} from '@okta/okta-react-native';
export default class ProfilePage extends React.Component {
constructor(props) {
super(props);
this.state = {
idToken: props.route.params.idToken,
isBrowserScenario: props.route.params.isBrowserScenario
};
}
logout = () => {
if (this.state.isBrowserScenario == true) {
signOut().then(() => {
this.props.navigation.popToTop();
}).catch(error => {
console.log(error);
});
}
Promise.all([revokeAccessToken(), revokeIdToken(), clearTokens()])
.then(() => {
this.props.navigation.popToTop();
}).catch(error => {
console.log(error);
});
}
render() {
return (
<View style={styles.container}>
<Text testID="welcome_text">Welcome back, {this.state.idToken.preferred_username}!</Text>
<Button
onPress={this.logout}
title="Logout"
testID="logout_button"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.