ReactJS - используйте iframe, чтобы молча обновить токен - PullRequest
0 голосов
/ 19 февраля 2020

Мой SPA имеет этот рабочий компонент, который выбирает токен доступа, который будет зашифрован и передан другим компонентам через props. Вот оно:

import React, { Component } from 'react';
//import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Credentials from './spotify-auth.js'
import './Spotify.css'


class SpotifyAuth extends Component {  
  constructor (props) {
    super(props);
    this.state = {
      isAuthenticatedWithSpotify: false,
    };
    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);
    }
    // DO STUFF WITH ACCEES TOKEN HERE
    this.props.onConnectWithSpotify(access_token); 
  };

  handleRedirect(event) {
    event.preventDefault()
    this.props.createMessage('You linked your Spotify account!', 'success');
    // get client features at authenticating
    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-3"><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;

Эти учетные данные токена сохраняются в течение 60 минут.

Я узнал, что стандартным вариантом для SPA является использование iframes для автоматического обновления токенов и вообще не использование refre sh токенов.


Как сделать один спин iframe и молча получать новый токен доступа каждый час в компоненте React, подобном приведенному выше? Я искал это везде и ничего не нашел.

1 Ответ

1 голос
/ 19 февраля 2020

Вы можете сделать следующее:

  1. Создать некоторую функцию-наблюдатель, которая проверяет время истечения токена доступа. Если срок действия токена истекает, пришло время его обновить.
  2. Для рендеринга тега iframe sr c должен быть тем же URL-адресом, который вы используете для перенаправления на сервер аутентификации, с одним отличием : измените URL возврата на файл * stati c, назовем его redirect.html. Сервер должен знать о пользователе, вызывающем этот URL, из сохраненного файла cook ie, поэтому он должен просто перенаправить вас в файл redirect.html, теперь с токеном fre sh.
  3. In redirect.html напишите короткий сценарий, который извлекает токен из URL-адреса и заменяет его на уже имеющийся в локальном хранилище.
  4. Уничтожьте iframe.

This об этом, токен обновлен. Поддерживайте работу наблюдателя и обновляйте его каждый раз, когда истекает срок его действия (делайте это за 5 минут до истечения срока его действия).

В качестве примера реализации компонента AccessToken в React большинство частей кода на самом деле будет работать, но вы нужно заменить константы на ваши вещи. Кроме того, некоторые функции, такие как extractTokenFromUrl, отсутствуют, но сделать это достаточно легко:

import React, { Component } from 'react'

export class SilentTokenRenew extends Component {
  constructor(props) {
    super(props)

    this.state = { renewing: false }
    this.currentAttempt = 0
    this.maxNumberOfAttempts = 20
  }

  shouldComponentUpdate(nextProps, nextState) {
    return this.state.renewing !== nextState.renewing
  }

  componentDidMount() {
    this.timeInterval = setInterval(this.handleCheckToken, 20000)
  }

  componentWillUnmount() {
    clearInterval(this.timeInterval)
  }

  willTokenExpire = () => {
    const token = YOUR_ACCESS_TOKEN_OBJECT // { accessToken, expirationTime }
    const threshold = 300 // 300s = 5 minute threshold for token expiration

    const hasToken = token && token.accessToken
    const now = (Date.now() / 1000) + threshold

    return !hasToken || (now > token.accessToken.expirationTime)
  }

  handleCheckToken = () => {
    if (this.willTokenExpire()) {
      this.setState({ renewing: true })
      clearInterval(this.timeInterval)
    }
  }

  silentRenew = () => {
    return new Promise((resolve, reject) => {
      const checkRedirect = () => {
        // This can be e
        const redirectUrl = localStorage[YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE] // /redirect.html#access_token=......
        if (!redirectUrl) {
          this.currentAttempt += 1
          if (this.currentAttempt > this.maxNumberOfAttempts) {
            reject({
              message: 'Silent renew failed after maximum number of attempts.',
              short: 'max_number_of_attempts_reached',
            })
            return
          }
          setTimeout(() => checkRedirect(), 500)
          return
        }

        // Clean up your localStorage for the next silent renewal
        localStorage.removeItem(YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE)

        // Put some more error handlers here

        // Silent renew worked as expected, lets update the access token
        const session = extractTokenFromUrl(redirectUrl) // write some function to get out the access token from the URL
        // Following your code you provided, here is the time to set
        // the extracted access token back to your localStorage under a key Credentials.stateKey
        localStorage.setItem(Credentials.stateKey, JSON.stringify(session))
        resolve(session)
      }

      checkRedirect()
    })
  }

  handleOnLoad = () => {
    this.silentRenew()
      .then(() => {
        this.setState({ renewing: false })
        this.currentAttempt = 0
        this.timeInterval = setInterval(this.handleCheckToken, 60000)
        // Access token renewed silently.
      })
      .catch(error => {
        this.setState({ renewing: false })
        // handle the errors
      })
  }

  renderIframe = () => {
    const url = new URL(YOUR_AUTHORIZE_URL_TO_TH_AUTH_SERVER)
    url.searchParams.set('redirect_uri', 'http://localhost:3000/redirect.html') // the redirect.html file location
    url.searchParams.set('prompt', 'none')

    return (
      <iframe
        style={{ width: 0, height: 0, position: 'absolute', left: 0, top: 0, display: 'none', visibility: 'hidden' }}
        width={0}
        height={0}
        title="silent-token-renew"
        src={url.href}
        onLoad={this.handleOnLoad}
      />
    )
  }

  render() {
    const { renewing } = this.state

    return renewing ? this.renderIframe() : null
  }
}

Пример кода для файла redirect.html:

<!DOCTYPE html>
<html>

<head>
    <title>OAuth - Redirect</title>
</head>

<body>
<p>Renewing...</p>
<script>
  // Get name of window which was set by the parent to be the unique request key
  // or if no parameter was specified, we have a silent renew from iframe
  const requestKey = YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE;
  // Update corresponding entry with the redirected url which should contain either access token or failure reason in the query parameter / hash
  window.localStorage.setItem(requestKey, window.location.href);
  window.close();
</script>
</body>

</html>
...