如何在浏览器中为 React SPA 保留 Auth0 登录状态 [英] How to persist Auth0 login status in browser for React SPA
问题描述
目前,当我创建路由时,我会检查 Auth0 方法 - isAuthenticated() - 以确定是否返回受保护的页面或重定向到登录.但是,此状态仅存在于内存中,并且不会在浏览器刷新时将用户保留在其页面上,我希望这样做.
这是一个 React/RR4/React Context 应用程序,我的 Auth0 方法列在 Auth.js 中(如下).
将登录状态存储在 localStorage 中是非常不可取的.如果我将 Auth0 令牌存储在 cookie 中,我不确定如何验证令牌,因为没有设置服务器验证.要检查的正确条件是什么才能实现安全数据持久性?
ProtectedRoutes.jsx:
<路由精确的键={route.path}路径={route.path}渲染={() =>(//检查条件context.auth.isAuthenticated()?(<div><route.component/>
) : <重定向到=/登录"/>)}/>
Auth.js(添加以供参考):
从'auth0-js'导入auth0;从'./auth0-variables'导入authConfig;类认证{访问令牌;身份令牌;到期;令牌更新超时;auth0 = 新的 auth0.WebAuth({域:authConfig.domain,客户端ID:authConfig.clientId,redirectUri: authConfig.callbackUrl,responseType: 'token id_token',范围:'openid'});构造函数(){this.scheduleRenewal();this.login = this.login.bind(this);this.logout = this.logout.bind(this);this.handleAuthentication = this.handleAuthentication.bind(this);this.isAuthenticated = this.isAuthenticated.bind(this);this.getAccessToken = this.getAccessToken.bind(this);this.getIdToken = this.getIdToken.bind(this);this.renewSession = this.renewSession.bind(this);this.scheduleRenewal = this.scheduleRenewal.bind(this);}登录() {console.log('登录!');this.auth0.authorize();}处理身份验证(){返回新的承诺((解决,拒绝)=> {this.auth0.parseHash((err, authResult) => {如果(错误)返回拒绝(错误);控制台日志(authResult);如果 (!authResult || !authResult.idToken) {返回拒绝(错误);}this.setSession(authResult);解决();});});}获取访问令牌(){返回 this.accessToken;}getIdToken() {返回 this.idToken;}getExpiration() {返回新日期(this.expiresAt);}isAuthenticated() {让 expiresAt = this.expiresAt;返回新日期().getTime() <到期;}setSession(authResult) {localStorage.setItem('isLoggedIn', 'true');让 expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();this.accessToken = authResult.accessToken;this.idToken = authResult.idToken;this.expiresAt = expiresAt;this.scheduleRenewal();}更新会话(){this.auth0.checkSession({}, (err, authResult) => {如果(authResult && authResult.accessToken && authResult.idToken){this.setSession(authResult);}否则如果(错误){this.logout();console.log(`无法获得新的令牌.(${err.error}: ${err.error_description})`);}});}计划更新(){让 expiresAt = this.expiresAt;const timeout = expiresAt - Date.now();如果(超时> 0){this.tokenRenewalTimeout = setTimeout(() => {this.renewSession();}, 暂停);}}登出() {this.accessToken = null;this.idToken = null;this.expiresAt = 0;localStorage.removeItem('isLoggedIn');clearTimeout(this.tokenRenewalTimeout);console.log('登出!');}}导出默认身份验证;
您可以使用 静默身份验证以在浏览器刷新时更新令牌.
专门用于您的 React SPA 应用
- 在您的主要应用组件中设置一个状态,将
tokenRenewed
设置为false
- 你的
auth.js
中已经有一个renewToken
方法,所以在componentDidMount
方法中调用它
componentDidMount() {this.auth.renewToken(() => {this.setState({tokenRenewed : true});})}
- 更新
renewToken
以接受如下回调cb
renewSession(cb) {this.auth0.checkSession({}, (err, authResult) => {如果(authResult && authResult.accessToken && authResult.idToken){this.setSession(authResult);}否则如果(错误){this.logout();console.log(`无法获得新的令牌.(${err.error}: ${err.error_description})`);}if(cb) cb(err, authResult);});}
- 确保除非
tokenRenewed
为true
,即除非您通过静默身份验证更新了有效令牌,否则不要加载应用程序组件
render() {if(!this.state.tokenRenewed) 返回加载中...";返回 (//你的应用组件);}
注意事项:
- 您可能需要确保在应用程序设置中设置了正确的
Allowed Web Origins
以使其正常工作 - 静默身份验证有一些限制,因为它需要在浏览器上启用 3rd 方 cookie,以防 Safari 的 ITP.您应该设置一个自定义域来避免这种情况.请参阅官方 auth0 文档以了解更多信息此处.
- 有关如何安全存储令牌的更多详细信息此处
Currently when I create my routes, I check an Auth0 method - isAuthenticated() - to determine whether or not to return a protected page or redirect to login. However, this state only exists in memory and does not keep a user on their page upon browser refresh and I would like to do so.
This is a React/RR4/React Context app and my Auth0 methods are listed in Auth.js (below).
It is highly inadvisable to store login state in localStorage. And if I store my Auth0 tokens in cookies, I'm not sure how I would validate the tokens since there is no server validation set up. What is the correct condition to check that will enable secure data persistence?
ProtectedRoutes.jsx:
<Route
exact
key={route.path}
path={route.path}
render={() => (
// CONDITION TO CHECK
context.auth.isAuthenticated()
? (
<div>
<route.component />
</div>
) : <Redirect to="/login" />
)}
/>
Auth.js (added for reference):
import auth0 from 'auth0-js';
import authConfig from './auth0-variables';
class Auth {
accessToken;
idToken;
expiresAt;
tokenRenewalTimeout;
auth0 = new auth0.WebAuth({
domain: authConfig.domain,
clientID: authConfig.clientId,
redirectUri: authConfig.callbackUrl,
responseType: 'token id_token',
scope: 'openid'
});
constructor() {
this.scheduleRenewal();
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
this.getAccessToken = this.getAccessToken.bind(this);
this.getIdToken = this.getIdToken.bind(this);
this.renewSession = this.renewSession.bind(this);
this.scheduleRenewal = this.scheduleRenewal.bind(this);
}
login() {
console.log('logging in!');
this.auth0.authorize();
}
handleAuthentication() {
return new Promise((resolve, reject) => {
this.auth0.parseHash((err, authResult) => {
if (err) return reject(err);
console.log(authResult);
if (!authResult || !authResult.idToken) {
return reject(err);
}
this.setSession(authResult);
resolve();
});
});
}
getAccessToken() {
return this.accessToken;
}
getIdToken() {
return this.idToken;
}
getExpiration() {
return new Date(this.expiresAt);
}
isAuthenticated() {
let expiresAt = this.expiresAt;
return new Date().getTime() < expiresAt;
}
setSession(authResult) {
localStorage.setItem('isLoggedIn', 'true');
let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
this.accessToken = authResult.accessToken;
this.idToken = authResult.idToken;
this.expiresAt = expiresAt;
this.scheduleRenewal();
}
renewSession() {
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.log(`Could not get a new token. (${err.error}: ${err.error_description})`);
}
});
}
scheduleRenewal() {
let expiresAt = this.expiresAt;
const timeout = expiresAt - Date.now();
if (timeout > 0) {
this.tokenRenewalTimeout = setTimeout(() => {
this.renewSession();
}, timeout);
}
}
logout() {
this.accessToken = null;
this.idToken = null;
this.expiresAt = 0;
localStorage.removeItem('isLoggedIn');
clearTimeout(this.tokenRenewalTimeout);
console.log('logged out!');
}
}
export default Auth;
You can use Silent authentication to renew the tokens on browser refresh.
Specifically for your react SPA app
- setup a state say
tokenRenewed
tofalse
in your main App component - you already have a
renewToken
method in yourauth.js
so call that incomponentDidMount
method
componentDidMount() {
this.auth.renewToken(() => {
this.setState({tokenRenewed : true});
})
}
- update
renewToken
to accept a callbackcb
like below
renewSession(cb) {
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.log(`Could not get a new token. (${err.error}: ${err.error_description})`);
}
if(cb) cb(err, authResult);
});
}
- Make sure you don't load the App component unless
tokenRenewed
istrue
i.e. unless you have the valid tokens renewed via silent authentication
render() {
if(!this.state.tokenRenewed) return "loading...";
return (
// Your App component
);
}
Notes:
- You may want to make sure you have the correct
Allowed Web Origins
set in the Application settings for this to work - Silent authentication has some limitations as in it requires 3rd party cookies enabled on the browser and in case ITP for Safari. You should setup a custom domain to avoid that. Refer to the official auth0 docs to learn more here.
- More details on how to store token securely here
这篇关于如何在浏览器中为 React SPA 保留 Auth0 登录状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!