best practices for authentication in react native

Viewed 1420

I'm a beginner in react native and I'm creating an app. I've done some research about how to make a secured react native app, but I didn't found much information. I've come up with a "solution" myself, but I want to make sure this is the right way to do this. So I need the help of some react native/javascript/security experts if possible, to quickly check if my approach is OK or not?

I have included 3 questions in this text, but obviously they're related. I've put them in bold. Feel free to answer one or more questions, I appreciate every answer!

I'm creating an app in react native. For a user to be able to use the app, the user should create an account and sign in. I'm using an JSON web token as an access token to authorize the requests made from the app to the server, and to identify the user (I store the user ID in the JSON web token).

At my server, I first check if the access token is valid. If so, I get the user ID out of the access token and use this user ID to identify the user.

For extra security, I'm also using refresh tokens, because an access token is only valid for 10 minutes. When a user send a request with an expired access token, the server responds with a 401 not authorized status.

To make my code more "managable", I've created a wrapper function in react native. I wrap every "request function" (every function where I do a GET/POST/PUT/DELETE request to the server) with this wrapper function. This wrapper function checks the response of the request. If the response status is 200, the response is returned to the code. If the response status is 401, the refresh token is send to a specific endpoint to obtain a new access token. When the access token arrives at the app, the previous request is made again with the new access token. The wrapper function also stores the new access token in (temporary) redux (keychain or shared preferences). 1. Is a wrapper function a good idea? For me, it's more manageble because now I'm reusing the code.

Every time the user opens the app, a new access token is requested, and when a user closes the app, the current access token is deleted, even if it is not expired yet. That way, I want to make sure that every app "session" starts with a new access token. 2. Is this okay? Or should I prevent unnecessary requests to the server when I still have a (possibly) valid access token?

In my react native app, this wrapper function is located in a context component. This "authentication" context is wrapper around my other components in App.js like this:

<AuthenticationProvider>
    <AppNavigator />
</AuthenticationProvider>

This way, my wrapper function is accessible to all my other components. My authentication context looks like this:

const AuthenticationContext = createContext({
    accessToken: null,
    wrapperFunction: () => {}
})

const AuthenticationProvider = (props) => {

    let accessToken = null

    const refreshToken = useSelector(state => state.auth.refreshToken)

    const wrapperFunction = () => {
        // wrapper function
        // set the access token
        // await fetch('server endpoint')...     
    }

    return (
        <AuthenticationContext.Provider value={{ accessToken, wrapperFunction }}>
        {props.children}
        </AuthenticationContext.Provider>
    )
}

3. Is using a context a good practice to do stuff like this?

Server-side, I store every refresh token in a database. When a user requests a new access token, I check if the sent request token still exists in the database. If not, I have revoked access for this user and the user should be logged out. This way, I want to make sure I can "manage" users.

2 Answers
  1. Yes, it makes sense. Actually I can't think of a better way to manage the scenario you mentioned. When you wanna temper the request before it's sent, you will need a single function to do so. You could also use some hooks e.g. onBeforeSend and onAfterReceive, but in your case I don't see any extra value for this.

  2. I do not agree with the deletion of a valid token. You can still send request to server on every app start to get user's last data -might have changed on another device-. I don't understand the logic of starting the app with a new session -maybe more information?

  3. I don't think you need to pass the wrapperFunction/token using context. It would be best if you could send user data by context. you wrapper function can access the token directly from asyncStorage. And each component can call the function directly by importing it.

  1. I believe you are taking the approach of using a wrapper function since the relevant API requests are made directly in components. The best practice is to move such requests outside (E.g. Redux actions with a middleware like redux-thunk) the components. It's better to check if the access token is expired (by decoding the token) before sending the API request and retrieve the new access token. This will reduce the amount of requests to server. You can implement a common request method which handle this check as well.
  2. I think since your access token expires every 10 mins this is unnecessary. Is there a specific reason to start each session with a new access token?
  3. You can pass in user access details using the context. I think it's matter of preference. Passing in the wrapper function is not needed if you're handing the requests through a common request method.
Related