Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
137 views
in Technique[技术] by (71.8m points)

javascript - React - What is the best way to handle login and authentication?

New to react and working on an application with authentication/logging in. It currently works but feels hacked together. Right now I have my isAuthenticated state located in my routes.js like so:

class Routes extends Component {

    constructor(props) {
        super(props);

        this.state = {
            isAuthenticated: false,
         }
     }

On my login page, I need to know when a user is authenticated to redirect them to the home page. What is the best design pattern to allow access and manipulation of this isAuthenticated state? How I currently have it set up is I have a function that sets the state inside the routes.js and sends the state as a prop like so:

 setAuthenticated = (isAuthenticated) => {
        this.setState({isAuthenticated});
    }

and down below in the router...

<Route path="/" exact component={() =>
                            <div>
                                <Login
                                    isAuthenticated={this.state.isAuthenticated}
                                    setAuthenticated={this.setAuthenticated}
                            </div>
                        } />

Yes, I understand this is bad design because this is changing props values which are supposed to be immutable. This is also bad because when I change this value in my login.js it causes multiple unnecessary re-renders. Should I be declaring isAuthenticated as some type of global variable? I am not using any state management by the way.

Edit: I am setting isAuthenticated based on a response from my server which confirms correct login/password combination.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Handling isAuthenticated only in the state means the user will be unauthenticated every time he refreshes the page. That's not really user-friendly! :)

So instead, the Login page should store an access_token (coming from your backend) in the cookies or localStorage of the browser. An access_token proves the user is authenticated and also verifies his identity. You will usually pass this access_token to every next requests to your server, to check if this user is allowed to access the data he's requesting, or allowed to create, edit and delete the things he's trying to create, edit and delete.

Then you can check this access_token on every other pages as well and redirect the user to the Login page if he's not authenticated anymore.


A brief aside on the difference between access_token and refresh_tokenthis will help you understand the code bellow, but feel free to skip ahead if you are already familiar with it.

Your backend probably uses OAuth2, which is the most common authentication protocol nowadays. With OAuth2, your app makes a first request to the server containing the username and password of the user to authenticate. Once the user is authenticated, he receives 1) an access_token, which usually expires after an hour, and 2) a refresh_token, which expires after a very long time (hours, days). When the access_token expires, instead of asking the user for his username and password again, your app sends the refresh_token to the server to obtain a new access_token for this user.


A brief aside on the differences between cookies and localStoragefeel free to skip it too!

localStorage is the most recent technology between both. It's a simple key/value persistence system, which seems perfect to store the access_token and its value. But we also need to persist its date of expiration. We could store a second key/value pair named expires but it would be more logic to handle on our side.

On the other hand, cookies have a native expires property, which is exactly what we need! cookies are an old technology and are not very developer-friendly, so I personally use js-cookie, which is a small library to manipulate cookies. It makes it look like a simple key/value persistence system too: Cookies.set('access_token', value) then Cookies.get('access_token').

Other pro for the cookies: they are cross subdomains! If your Login app is login.mycompany.com and your Main app is app.mycompany.com, then you can create a cookie on the Login app and access it from the Main app. This is not possible with LocalStorage.


Here are some of the methods and special React components I use for authentication:

isAuthenticated()

import Cookies from 'js-cookie'

export const getAccessToken = () => Cookies.get('access_token')
export const getRefreshToken = () => Cookies.get('refresh_token')
export const isAuthenticated = () => !!getAccessToken()

authenticate()

export const authenticate = async () => {
  if (getRefreshToken()) {
    try {
      const tokens = await refreshTokens() // call an API, returns tokens

      const expires = (tokens.expires_in || 60 * 60) * 1000
      const inOneHour = new Date(new Date().getTime() + expires)

      // you will have the exact same setters in your Login page/app too
      Cookies.set('access_token', tokens.access_token, { expires: inOneHour })
      Cookies.set('refresh_token', tokens.refresh_token)

      return true
    } catch (error) {
      redirectToLogin()
      return false
    }
  }

  redirectToLogin()
  return false
}

redirectToLogin()

const redirectToLogin = () => {
  window.location.replace(
    `${getConfig().LOGIN_URL}?next=${window.location.href}`
  )
  // or history.push('/login') if your Login page is inside the same app
}

AuthenticatedRoute

export const AuthenticatedRoute = ({
  component: Component,
  exact,
  path,
}) => (
  <Route
    exact={exact}
    path={path}
    render={props =>
      isAuthenticated() ? (
        <Component {...props} />
      ) : (
        <AuthenticateBeforeRender render={() => <Component {...props} />} />
      )
    }
  />
)

AuthenticateBeforeRender

class AuthenticateBeforeRender extends Component {
  state = {
    isAuthenticated: false,
  }

  componentDidMount() {
    authenticate().then(isAuthenticated => {
      this.setState({ isAuthenticated })
    })
  }

  render() {
    return this.state.isAuthenticated ? this.props.render() : null
  }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...