在 React Native 中处理刷新令牌

Posted

技术标签:

【中文标题】在 React Native 中处理刷新令牌【英文标题】:Handling Refresh Token in React Native 【发布时间】:2020-01-22 20:54:01 【问题描述】:

我有一个应用程序验证正常并返回access_tokenrefresh_token。我用AsyncStorage 存储它们,并用redux 保存/获取access_token。这是我正在构建的第一个应用程序,我正在努力解决如何以及在何处使用 refresh_token

这是组件loginForm.js中的axios调用

axios(
                url: `$base/oauth/token`,
                method: 'POST',
                data: formData,
                headers: 
                    Accept: 'application/json',
                    'Content-Type': 'multipart/form-data',
                
            )
            .then(response => 
                setStatus( succeeded: true );
                // console.log(response.data);
                deviceStorage.saveKey("userToken", response.data.access_token);
                deviceStorage.saveKey("refreshToken", response.data.refresh_token);
                Actions.main();
            )
            .catch(error => 
                if (error.response) 
                    console.log(error);
                
            );

这是deviceStorage.js服务

import  AsyncStorage  from 'react-native';

const deviceStorage = 
    async saveItem(key, value) 
        try 
            await AsyncStorage.setItem(key, value);
         catch (error) 
            console.log('AsyncStorage Error: ' + error.message);
        
    
;

export default deviceStorage;

这是令牌动作文件

import  AsyncStorage  from 'react-native';
import 
    GET_TOKEN,
    SAVE_TOKEN,
    REMOVE_TOKEN,
    LOADING_TOKEN,
    ERROR_TOKEN
 from '../types';

export const getToken = token => (
    type: GET_TOKEN,
    token,
);

export const saveToken = token => (
    type: SAVE_TOKEN,
    token
);

export const removeToken = () => (
    type: REMOVE_TOKEN,
);

export const loading = bool => (
    type: LOADING_TOKEN,
    isLoading: bool,
);

export const error = tokenError => (
    type: ERROR_TOKEN,
    tokenError,
);

export const getUserToken = () => dispatch => 
    AsyncStorage.getItem('userToken')
        .then((data) => 
            dispatch(loading(false));
            dispatch(getToken(data));
        )
        .catch((err) => 
            dispatch(loading(false));
            dispatch(error(err.message || 'ERROR'));
        );

export const saveUserToken = (data) => dispatch =>
    AsyncStorage.setItem('userToken', data)
        .then(() => 
            dispatch(loading(false));
            dispatch(saveToken('token saved'));
        )
        .catch((err) => 
            dispatch(loading(false));
            dispatch(error(err.message || 'ERROR'));
        );

export const removeUserToken = () => dispatch =>
    AsyncStorage.removeItem('userToken')
        .then((data) => 
            dispatch(loading(false));
            dispatch(removeToken(data));
        )
        .catch((err) => 
            dispatch(loading(false));
            dispatch(error(err.message || 'ERROR'));
        );

这是令牌缩减文件

import 
    GET_TOKEN,
    SAVE_TOKEN,
    REMOVE_TOKEN,
    LOADING_TOKEN,
    ERROR_TOKEN
 from '../actions/types';

const INITIAL_STATE = 
    token: ,
    loading: true,
    error: null
;

export default (state = INITIAL_STATE, action) => 
    switch (action.type) 
        case GET_TOKEN:
            return 
                ...state,
                token: action.token
            ;
        case SAVE_TOKEN:
            return 
                ...state,
                token: action.token
            ;
        case REMOVE_TOKEN:
            return 
                ...state,
                token: action.token
            ;
        case LOADING_TOKEN:
            return 
                ...state,
                loading: action.isLoading
            ;
        case ERROR_TOKEN:
            return 
                ...state,
                error: action.error
            ;
        default:
            return state;
    
;

这是认证文件

import React from 'react';
import 
    StatusBar,
    StyleSheet,
    View,
 from 'react-native';
import  connect  from 'react-redux';
import  Actions  from 'react-native-router-flux';
import  Spinner  from '../common';
import  getUserToken  from '../../actions';

class AuthLoadingScreen extends React.Component 

    componentDidMount() 
        this.bootstrapAsync();
    

    bootstrapAsync = () => 
        this.props.getUserToken().then(() => 
            if (this.props.token.token !== null) 
                Actions.main();
             else 
                Actions.auth();
            
        )
            .catch(error => 
                this.setState( error );
            );
    ;

    render() 
        return (
            <View style=styles.container>
                <Spinner />
                <StatusBar barStyle="default" />
            </View>
        );
    


const styles = StyleSheet.create(
    container: 
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center'
    ,
);

const mapStateToProps = state => (
    token: state.token,
);


const mapDispatchToProps = dispatch => (
    getUserToken: () => dispatch(getUserToken()),
);

export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);

我相信我需要创建一个动作和减速器来获取refresh_token(对吗?)但我不知道如何处理它以及在哪里调用它(可能在身份验证文件中?)。 任何可能与我的代码相关的代码示例的帮助将不胜感激。谢谢

【问题讨论】:

您在这方面取得了进展吗?我也有同样的问题 您使用的是哪种身份验证? Google 登录、Facebook 登录、firebase 身份验证或您的自定义身份验证系统? 【参考方案1】:

以下是步骤

登录,从响应中获取 accessToken 和 refreshToken 并将其保存到 AsyncStorage。 为API调用制作通用函数

    async function makeRequest(method, url, params, type) 
      const token = await AsyncStorage.getItem('access_token');
    
      let options = 
        method: method,
        headers: 
          Accept: 'application/json',
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + token,
        ,
      ;
    
      if (!token) 
        delete options['Authorization'];
      
    
      if (['GET', 'OPTIONS'].includes(method)) 
        url += (url.indexOf('?') === -1 ? '?' : '&') + queryParams(params);
       else 
        Object.assign(options, body: JSON.stringify(params));
      
    
      const response = fetch(ENV.API_URL+url, options);
    
      return response;
    

在 redux 中为 getAceessTokenFromRefreshToken 创建一个方法。 会话过期时使用此方法

你怎么知道会话过期了?

如果您收到类似(440 响应代码)的响应,则从每个 API 调用


    async componentWillReceiveProps(nextProps) 
        if (nextProps.followResponse && nextProps.followResponse != this.props.followResponse) 
          if (nextProps.followResponse.status) 
          
      if (nextProps.followResponse.status == 440) 
                // call here get acceesstokenfrom refresh token method and save again accesstoken in asyncstorage and continue calling to API
            
          
        
      

【讨论】:

以上是关于在 React Native 中处理刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

用于 JWT 刷新令牌的 react-native 中的 httpOnly cookie

当 jwt 刷新令牌未过期时,React Native 应用程序注销

使用 redux saga 在 react native 中实现令牌刷新

在 React-Native 中获取 FCM 令牌

React Native 中的 Auth0 刷新令牌失败并显示 401

在 React Native 中保存敏感数据