如何在 React SPA 的浏览器中保留 Auth0 登录状态
How to persist Auth0 login status in browser for React SPA
目前,当我创建我的路由时,我会检查 Auth0 方法 - isAuthenticated() - 以确定是否 return 受保护的页面或重定向到登录。然而,这种状态只存在于内存中,不会在浏览器刷新时让用户留在他们的页面上,我想这样做。
这是一个 React/RR4/React 上下文应用程序,我的 Auth0 方法列在 Auth.js(下方)中。
极不建议将登录状态存储在 localStorage 中。如果我将我的 Auth0 令牌存储在 cookie 中,我不确定我将如何验证令牌,因为没有设置服务器验证。启用安全数据持久性的正确检查条件是什么?
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(添加以供参考):
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;
您可以使用 Silent authentication 在浏览器刷新时更新令牌。
专为您的 React SPA 应用程序
- 在您的主要 App 组件中设置状态说
tokenRenewed
到 false
- 您的
auth.js
中已经有一个 renewToken
方法,因此请在 componentDidMount
方法中调用它
componentDidMount() {
this.auth.renewToken(() => {
this.setState({tokenRenewed : true});
})
}
- 更新
renewToken
以接受回调 cb
如下所示
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);
});
}
- 确保您不加载 App 组件,除非
tokenRenewed
是 true
,即除非您通过静默身份验证更新了有效令牌
render() {
if(!this.state.tokenRenewed) return "loading...";
return (
// Your App component
);
}
备注:
我想在这里引用官方 Auth0 文档。
The Auth0 SPA SDK stores tokens in memory by default. However, this
does not provide persistence across page refreshes and browser tabs.
Instead, you can opt-in to store tokens in local storage by setting
the cacheLocation
property to localstorage
when initializing the SDK.
This can help to mitigate some of the effects of browser privacy
technology that prevents access to the Auth0 session cookie by storing
Access Tokens for longer.
Storing tokens in browser local storage provides persistence across
page refreshes and browser tabs. However, if an attacker can achieve
running JavaScript in the SPA using a cross-site scripting (XSS)
attack, they can retrieve the tokens stored in local storage. A
vulnerability leading to a successful XSS attack can be either in the
SPA source code or in any third-party JavaScript code (such as
bootstrap, jQuery, or Google Analytics) included in the SPA.
简而言之,文档从未说过存储令牌是本地存储本身就是一个安全问题。 XSS 是 漏洞(并且它与应用程序中的 auth0 实现无关),因为窃取用户令牌只是攻击者可以做的一长串坏事中的一件事。
我喜欢接受的答案,因为它为我遇到的 auth0 问题提供了有效的解决方案。但我不认为刷新应该在每次页面重新加载时而不是在到期时进行。
我在不同的浏览器中也有不同的行为。此注销问题偶尔会在 chrome 中发生,并且在 safari 中每隔一秒就会发生一次。我认为实施必须可靠,并且对所有流行的 browsers/versions.
始终如一地工作
如何应用设置的示例:
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={redirectUri}
onRedirectCallback={onRedirectCallback}
audience={audience}
// Possible values: memory, localstorage, sessionstorage or cookies
cacheLocation={'localstorage'}
>
{children}
</Auth0Provider>
目前,当我创建我的路由时,我会检查 Auth0 方法 - isAuthenticated() - 以确定是否 return 受保护的页面或重定向到登录。然而,这种状态只存在于内存中,不会在浏览器刷新时让用户留在他们的页面上,我想这样做。
这是一个 React/RR4/React 上下文应用程序,我的 Auth0 方法列在 Auth.js(下方)中。
极不建议将登录状态存储在 localStorage 中。如果我将我的 Auth0 令牌存储在 cookie 中,我不确定我将如何验证令牌,因为没有设置服务器验证。启用安全数据持久性的正确检查条件是什么?
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(添加以供参考):
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;
您可以使用 Silent authentication 在浏览器刷新时更新令牌。
专为您的 React SPA 应用程序
- 在您的主要 App 组件中设置状态说
tokenRenewed
到false
- 您的
auth.js
中已经有一个renewToken
方法,因此请在componentDidMount
方法中调用它
componentDidMount() {
this.auth.renewToken(() => {
this.setState({tokenRenewed : true});
})
}
- 更新
renewToken
以接受回调cb
如下所示
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);
});
}
- 确保您不加载 App 组件,除非
tokenRenewed
是true
,即除非您通过静默身份验证更新了有效令牌
render() {
if(!this.state.tokenRenewed) return "loading...";
return (
// Your App component
);
}
备注:
我想在这里引用官方 Auth0 文档。
The Auth0 SPA SDK stores tokens in memory by default. However, this does not provide persistence across page refreshes and browser tabs. Instead, you can opt-in to store tokens in local storage by setting the
cacheLocation
property tolocalstorage
when initializing the SDK. This can help to mitigate some of the effects of browser privacy technology that prevents access to the Auth0 session cookie by storing Access Tokens for longer.
Storing tokens in browser local storage provides persistence across page refreshes and browser tabs. However, if an attacker can achieve running JavaScript in the SPA using a cross-site scripting (XSS) attack, they can retrieve the tokens stored in local storage. A vulnerability leading to a successful XSS attack can be either in the SPA source code or in any third-party JavaScript code (such as bootstrap, jQuery, or Google Analytics) included in the SPA.
简而言之,文档从未说过存储令牌是本地存储本身就是一个安全问题。 XSS 是 漏洞(并且它与应用程序中的 auth0 实现无关),因为窃取用户令牌只是攻击者可以做的一长串坏事中的一件事。
我喜欢接受的答案,因为它为我遇到的 auth0 问题提供了有效的解决方案。但我不认为刷新应该在每次页面重新加载时而不是在到期时进行。
我在不同的浏览器中也有不同的行为。此注销问题偶尔会在 chrome 中发生,并且在 safari 中每隔一秒就会发生一次。我认为实施必须可靠,并且对所有流行的 browsers/versions.
始终如一地工作如何应用设置的示例:
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={redirectUri}
onRedirectCallback={onRedirectCallback}
audience={audience}
// Possible values: memory, localstorage, sessionstorage or cookies
cacheLocation={'localstorage'}
>
{children}
</Auth0Provider>