簡體   English   中英

Okta認證,response.params如何使用?

[英]Okta authentication, how to use the response.params?

我們正在嘗試使用 Okta 的 Expo 身份驗證,如下所述:

https://docs.expo.dev/guides/authentication/#okta

Expo 有很多很好的文檔,但不幸的是,對於 Okta 身份驗證,我們無法弄清楚如何以正確的方式使用該庫。

目前,我們遇到了很多麻煩(主要是因為 Okta 配置頁面中的歧義),我們來到了一個特定的點,下面的代碼正確地響應了code參數。 這與 Expo 文檔中的部分完全相同:

React.useEffect(() => {
    if (response?.type === 'success') {
      const { code } = response.params;
    }
  }, [response]);

但不幸的是,我們找不到任何方法可以使用參數code來獲取范圍信息、電子郵件、姓名等...

誰能指導我們如何使用目標code來檢索這些數據? (Okta 文檔對此也不清楚,所以我們被卡住了。)

編輯 1:

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
}

編輯 2:

調用exchangeCodeAsync也會產生錯誤。

代碼:

    const tokenRequestParams = {
        code: code,
        clientId: config.okta.clientId,
        redirectUri: oktaRedirectUri,
        extraParams: {
            code_verifier: authRequest.codeVerifier
        },
    }

    const tokenResult = await exchangeCodeAsync(tokenRequestParams, discovery);

錯誤:

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:我也在世博論壇問了同樣的問題 如果我們能在那里解決,我打算在這里也為更廣泛的觀眾反映。 (該方法可能與 Okta 有關,而不是 Expo 本身。)

在 React Native 中有兩種使用 Okta 的方法

1. 通過 Restful API,使用 fetch/Axios

2. 使用原生SDK

通過 Restful API,使用 fetch/Axios

這是使用 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>
  );
};

就是這樣,我們可以獲取交換令牌,然后使用 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)
}

這就是我們如何使用 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,
  },
});

參考代碼

通過使用原生 SDK

對於本機 SDK,您可以像這樣使用okta-react-native

登錄屏幕

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,
  }
});

配置文件屏幕

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM