如何在浏览器中为 React SPA 保留 Auth0 登录状态 [英] How to persist Auth0 login status in browser for React SPA

查看:25
本文介绍了如何在浏览器中为 React SPA 保留 Auth0 登录状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,当我创建路由时,我会检查 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);});}

  • 确保除非 tokenRenewedtrue,即除非您通过静默身份验证更新了有效令牌,否则不要加载应用程序组件

render() {if(!this.state.tokenRenewed) 返回加载中...";返回 (//你的应用组件);}

注意事项:

  1. 您可能需要确保在应用程序设置中设置了正确的Allowed Web Origins 以使其正常工作
  2. 静默身份验证有一些限制,因为它需要在浏览器上启用 3rd 方 cookie,以防 Safari 的 ITP.您应该设置一个自定义域来避免这种情况.请参阅官方 auth0 文档以了解更多信息此处.
  3. 有关如何安全存储令牌的更多详细信息此处

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 to false in your main App component
  • you already have a renewToken method in your auth.js so call that in componentDidMount method

componentDidMount() {
   this.auth.renewToken(() => {
      this.setState({tokenRenewed : true});
   })
}

  • update renewToken to accept a callback cb 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 is true i.e. unless you have the valid tokens renewed via silent authentication

render() {
    if(!this.state.tokenRenewed) return "loading...";
    return (
      // Your App component
    );
}

Notes:

  1. You may want to make sure you have the correct Allowed Web Origins set in the Application settings for this to work
  2. 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.
  3. More details on how to store token securely here

这篇关于如何在浏览器中为 React SPA 保留 Auth0 登录状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆