web-dev-qa-db-fra.com

Flux implicite avec rafraîchissement silencieux dans React

Contexte

Je teste Implicit Flow auth dans mon React app et j'essaye d'implémenter ce que l'on appelle Silent Refresh capacités, où je demande périodiquement un nouveau jeton d'accès pendant que l'utilisateur est connecté, sans avoir besoin de lui demander une nouvelle autorisation.

Voici le schéma Flow, où le Auth0 Tenant, dans mon cas, c'est Spotify:

enter image description here

Bien que les SPA (applications d'une seule page) utilisant la subvention implicite ne peuvent pas utilisent des jetons d'actualisation, il existe d'autres façons de fournir des fonctionnalités similaires:

  • Utilisation Prompt=none lors de l'appel du /authorize point final. L'utilisateur ne verra pas les boîtes de dialogue de connexion ou de consentement.

  • Appel /authorize à partir d'un iframe caché et extraire le nouveau jeton d'accès du cadre parent. L'utilisateur ne verra pas les redirections se produire.


Une autre approche consiste à implémenter quelque chose comme le paquet axios-auth-refresh , une bibliothèque qui

vous aide à implémenter l'actualisation automatique des autorisations via les intercepteurs axios. Vous pouvez facilement intercepter la demande d'origine en cas d'échec, actualiser l'autorisation et poursuivre la demande d'origine, sans aucune intervention de l'utilisateur.

Utilisation :

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(/* ... */);

Configuration

Il s'agit de mon composant Parent (veuillez noter que l'état isAuthenticated fait référence à l'authentification de mon application, sans rapport avec le jeton Spotify dont j'ai besoin Actualisation silencieuse ):

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;

et ce qui suit est mon composant SpotifyAuth, par lequel l'utilisateur clique sur un bouton afin d'autoriser et d'authentifier son compte Spotify avec l'application lorsqu'il se connecte.

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;

Cependant, Silent Refresh n'aurait pas besoin du bouton ci-dessus, ni de rendre quoi que ce soit.


Par souci d'exhaustivité, il s'agit du point de terminaison que j'utilise pour le processus d'authentification de mon application, qui utilise des jetons Web jwt -json pour chiffrer les jetons et les transmettre via des cookies du serveur au client (mais cet outil de chiffrement n'est pas utilisé pour le jeton Spotify transmis à mon client, jusqu'à présent):

@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

[~ # ~] question [~ # ~]

Compte tenu des options et du code ci-dessus, comment puis-je utiliser ma configuration pour ajouter une actualisation silencieuse et gérer la redirection vers Spotify et obtenir un nouveau jeton toutes les heures en arrière-plan?

Quelque chose qui se situe entre cette solution et mon code?

6
8-Bit Borges

Il ne m'est pas immédiatement clair que vous pourrez faire une demande d'authentification silencieuse avec l'API spotify. Selon leur guide d'autorisation le flux implicite est temporaire et n'inclut pas l'actualisation du jeton.

Dans le contexte d'Auth0, vous utiliseriez un iFrame pour envoyer une demande silencieuse avec le cookie des utilisateurs, où une session validerait la demande et émettrait un nouveau jeton d'accès. Cela se fait en utilisant l'option Prompt = none comme vous l'avez mentionné ci-dessus.

0
Dan Woda