【发布时间】:2020-04-20 04:11:53
【问题描述】:
背景
我正在我的 React 应用程序中测试 Implicit Flow auth 并尝试实现所谓的 静默刷新 功能,我会在用户登录时定期请求新的访问令牌,而无需需要向他申请新的授权。
以下是 Flow 架构,在我的例子中,Auth0 Tenant 是 Spotify:
虽然使用隐式授权的 SPA(单页应用程序)不能使用刷新令牌,但还有其他方法可以提供类似的功能:
在调用
/authorize端点时使用prompt=none。用户将 看不到登录或同意对话框。从隐藏的 iframe 调用
/authorize并 从父框架中提取新的访问令牌。用户不会 查看正在发生的重定向。
另一种方法是实现类似于包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(/* ... */);
设置
这是我的Parent 组件(请注意,isAuthenticated 状态指的是我的应用身份验证,与我需要 静默刷新 的 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;
以下是我的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;
然而,静默刷新不需要上面的按钮,也不需要渲染任何东西。
为了完整起见,这是我用于我的应用程序身份验证过程的端点,它使用 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
问题
考虑到上面的选项和代码,我如何使用我的设置来添加静默刷新并处理重定向到 Spotify 并在后台每小时获取一个新令牌?
位于this solution 和我的代码之间的东西?
【问题讨论】:
-
您是否尝试过使用 iframe 静默刷新您的访问令牌?
-
不,这就是我要问的:如何准确地做到这一点。
-
您需要引入一个隐藏的 iframe,并从那里处理重定向流,然后在您的 iframe 哈希中获取 access_token 后,将其存储在您的应用程序中。我目前正在这样做,我获得了一个新令牌,但是 iframe 有问题 检查我今天发布的问题,也许它会给你一个提示。 stackoverflow.com/questions/59656972/…
标签: reactjs axios spotify auth0 refresh-token