React 中具有静默刷新的隐式流

dat*_*den 9 spotify reactjs auth0 axios refresh-token

背景

我正在我的 React 应用程序中测试隐式流身份验证,并尝试实现所谓的 静默刷新功能,在用户登录时,我定期要求一个新的访问令牌,而无需要求他提供新的授权。

以下是 Flow 模式Auth0 Tenant,在我的例子中,是 Spotify:

在此处输入图片说明

虽然使用隐式授权的 SPA(单页应用程序)不能使用刷新令牌,但还有其他方法可以提供类似的功能:

  • prompt=none调用/authorize端点时使用。用户不会看到登录或同意对话框。

  • /authorize从隐藏的 iframe调用并从父框架中提取新的访问令牌。用户不会看到重定向发生。


另一种方法是实现类似包axios-auth-refresh 的东西,一个库

帮助您通过axios拦截器实现授权的自动刷新。当原始请求失败时,您可以轻松拦截原始请求,刷新授权并继续原始请求,无需任何用户交互。

用法

import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';

// Function that will be called to refresh authorization
const refreshAuthLogic = failedRequest => axios.post('https://www.example.com/auth/token/refresh').then(tokenRefreshResponse => {
    localStorage.setItem('token', tokenRefreshResponse.data.token);
    failedRequest.response.config.headers['Authorization'] = 'Bearer ' + tokenRefreshResponse.data.token;
    return Promise.resolve();
});

// Instantiate the interceptor (you can chain it as it returns the axios instance)
createAuthRefreshInterceptor(axios, refreshAuthLogic);

// Make a call. If it returns a 401 error, the refreshAuthLogic will be run, 
// and the request retried with the new token
axios.get('https://www.example.com/restricted/area')
    .then(/* ... */)
    .catch(/* ... */);
Run Code Online (Sandbox Code Playgroud)

设置

这是我的Parent组件(请注意,isAuthenticated状态指的是我的应用程序身份验证,与Silent Refresh所需的 Spotify 令牌无关):

import SpotifyAuth from './components/spotify/Spotify';

class App extends Component {
  constructor() {
    super();
    this.state = {
      isAuthenticated: false,
      isAuthenticatedWithSpotify: false,
      spotifyToken: '',
      tokenRenewed:'' 
    };
    this.logoutUser = this.logoutUser.bind(this);
    this.loginUser = this.loginUser.bind(this);
    this.onConnectWithSpotify = this.onConnectWithSpotify.bind(this);
  };

  UNSAFE_componentWillMount() {
    if (window.localStorage.getItem('authToken')) {
      this.setState({ isAuthenticated: true });
    };
  };

  logoutUser() {
    window.localStorage.clear();
    this.setState({ isAuthenticated: false });
  };

  loginUser(token) {
    window.localStorage.setItem('authToken', token);
    this.setState({ isAuthenticated: true });
  };

  onConnectWithSpotify(token){
    this.setState({ spotifyToken: token,
                    isAuthenticatedWithSpotify: true
    }, () => {
       console.log('Spotify Token', this.state.spotifyToken)
    });
  }

  render() {
    return (
      <div>
        <NavBar
          title={this.state.title}
          isAuthenticated={this.state.isAuthenticated}
        />
        <section className="section">
          <div className="container">
            <div className="columns">
              <div className="column is-half">
                <br/>
                <Switch>
                  <Route exact path='/' render={() => (
                    <SpotifyAuth
                    onConnectWithSpotify={this.onConnectWithSpotify}
                    spotifyToken={this.state.spotifyToken}
                    />
                  )} />
                  <Route exact path='/login' render={() => (
                    <Form
                      formType={'Login'}
                      isAuthenticated={this.state.isAuthenticated}
                      loginUser={this.loginUser}
                      userId={this.state.id} 
                    />
                  )} />
                  <Route exact path='/logout' render={() => (
                    <Logout
                      logoutUser={this.logoutUser}
                      isAuthenticated={this.state.isAuthenticated}
                      spotifyToken={this.state.spotifyToken}
                    />
                  )} />
                </Switch>
              </div>
            </div>
          </div>
        </section>
      </div>
    )
  }
};

export default App;
Run Code Online (Sandbox Code Playgroud)

以下是我的SpotifyAuth组件,当用户登录时,用户单击按钮以通过应用程序授权和验证他的 Spotify 帐户。

import Credentials from './spotify-auth.js'
import './Spotify.css'

class SpotifyAuth extends Component {  
  constructor (props) {
    super(props);
    this.state = {
      isAuthenticatedWithSpotify: this.props.isAuthenticatedWithSpotify
    };
    this.state.handleRedirect = this.handleRedirect.bind(this);
  };

  generateRandomString(length) {
    let text = '';
    const possible =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (let i = 0; i < length; i++) {
      text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
    } 

  getHashParams() {
    const hashParams = {};
    const r = /([^&;=]+)=?([^&;]*)/g;
    const q = window.location.hash.substring(1);
    let e = r.exec(q);
    while (e) {
      hashParams[e[1]] = decodeURIComponent(e[2]);
      e = r.exec(q);
    }
    return hashParams;
  }

  componentDidMount() {
    //if (this.props.isAuthenticated) {
    const params = this.getHashParams();

    const access_token = params.access_token;
    const state = params.state;
    const storedState = localStorage.getItem(Credentials.stateKey);
    localStorage.setItem('spotifyAuthToken', access_token);
    localStorage.getItem('spotifyAuthToken');

    if (window.localStorage.getItem('authToken')) {
      this.setState({ isAuthenticatedWithSpotify: true });
    };
    if (access_token && (state == null || state !== storedState)) {
      alert('Click "ok" to finish authentication with Spotify');
    } else {
      localStorage.removeItem(Credentials.stateKey);
    }
    this.props.onConnectWithSpotify(access_token); 
  };


  handleRedirect(event) {
    event.preventDefault()
    const params = this.getHashParams();
    const access_token = params.access_token;
    console.log(access_token);

    const state = this.generateRandomString(16);
    localStorage.setItem(Credentials.stateKey, state);

    let url = 'https://accounts.spotify.com/authorize';
    url += '?response_type=token';
    url += '&client_id=' + encodeURIComponent(Credentials.client_id);
    url += '&scope=' + encodeURIComponent(Credentials.scope);
    url += '&redirect_uri=' + encodeURIComponent(Credentials.redirect_uri);
    url += '&state=' + encodeURIComponent(state);
    window.location = url; 
  };

  render() {
      return (
        <div className="button_container">
            <h1 className="title is-4"><font color="#C86428">Welcome</font></h1>
            <div className="Line" /><br/>
              <button className="sp_button" onClick={(event) => this.handleRedirect(event)}>
                <strong>LINK YOUR SPOTIFY ACCOUNT</strong>
              </button>
        </div>
      )
  }
}
export default SpotifyAuth;
Run Code Online (Sandbox Code Playgroud)

然而,Silent Refresh 不需要上面的按钮,也不需要渲染任何东西。


为完整起见,这是我用于我的应用程序身份验证过程的端点,它使用jwt-json Web 令牌来加密令牌并通过 cookie 从服务器到客户端将它们传递(但此加密工具未用于正在传递的 Spotify 令牌给我的客户,到目前为止):

@auth_blueprint.route('/auth/login', methods=['POST'])
def login_user():
    # get post data
    post_data = request.get_json()
    response_object = {
        'status': 'fail',
        'message': 'Invalid payload.'
    }
    if not post_data:
        return jsonify(response_object), 400
    email = post_data.get('email')
    password = post_data.get('password')
    try:
        user = User.query.filter_by(email=email).first()
        if user and bcrypt.check_password_hash(user.password, password):
            auth_token = user.encode_auth_token(user.id)
            if auth_token:
                response_object['status'] = 'success'
                response_object['message'] = 'Successfully logged in.'
                response_object['auth_token'] = auth_token.decode()
                return jsonify(response_object), 200
        else:
            response_object['message'] = 'User does not exist.'
            return jsonify(response_object), 404
    except Exception:
        response_object['message'] = 'Try again.'
        return jsonify(response_object), 500
Run Code Online (Sandbox Code Playgroud)

考虑到上面的选项和代码,我如何使用我的设置来添加静默刷新并处理重定向到 Spotify 并在后台每小时获取一个新令牌?

介于此解决方案和我的代码之间的东西?

小智 0

我目前还不清楚您是否能够使用 Spotify API 进行静默身份验证请求。根据他们的授权指南,隐式流程是临时的,不包括刷新令牌。

在 Auth0 的上下文中,您将利用 iFrame 发送带有用户 cookie 的静默请求,其中会话将验证该请求并发出新的访问令牌。这是使用上面提到的prompt=none 选项来完成的。