26

I'm looking for a way to create a basic authentication for my react-native app. I couldn't find any good example for react-native app.

  • To login, the app sends the email/password + clientSecret to my server
  • If OK, the server returns accessToken + refreshToken
  • The user is logged in, all other requests include the bearer with the accessToken.
  • If the accessToken expires, the app requests a new one with the refreshToken automatically.
  • The user stays logged in all the time, the states should be saved in the phone.

What would be the best approach for this?

Thanks.

1
  • Can't write up a full example for you, but check out JSON web tokens as what you pass between client and server. This will allow you to do DB-less roles and the expiration behavior your want. Commented Mar 3, 2016 at 21:25

3 Answers 3

61
+50

When an app communicates with a HTTP API which enforces some form of authentication, the app typically follows these steps:

  1. The app is not authenticated, so we prompt the user to log in.
  2. The user enters their credentials (username and password), and taps submit.
  3. We send these credentials to the API, and inspect the response:
    • On success (200 - OK): We cache the authentication token/ hash, because we're going to use this token/ hash in every subsequent request.
      • If the token/ hash does not work during any of the subsequent API requests (401 - Unauthorized), we'll need to invalidate the hash/ token and prompt the user to log in again.
    • Or, on failure (401 - Unauthorized): We display an error message to the user, prompting them re-enter their credentials.

Logging In

Based on the work flow defined above our app starts by displaying a login form, step 2 kicks in when the user taps the login button which dispatches the login action creator below:

/// actions/user.js

export function login(username, password) {
  return (dispatch) => {

    // We use this to update the store state of `isLoggingIn`          
    // which can be used to display an activity indicator on the login
    // view.
    dispatch(loginRequest())

    // Note: This base64 encode method only works in NodeJS, so use an
    // implementation that works for your platform:
    // `base64-js` for React Native,
    // `btoa()` for browsers, etc...
    const hash = new Buffer(`${username}:${password}`).toString('base64')
    return fetch('https://httpbin.org/basic-auth/admin/secret', {
      headers: {
        'Authorization': `Basic ${hash}`
      }
    })
    .then(response => response.json().then(json => ({ json, response })))
    .then(({json, response}) => {
      if (response.ok === false) {
        return Promise.reject(json)
      }
      return json
    })
    .then(
      data => {
        // data = { authenticated: true, user: 'admin' }
        // We pass the `authentication hash` down to the reducer so that it
        // can be used in subsequent API requests.

        dispatch(loginSuccess(hash, data.user))
      },
      (data) => dispatch(loginFailure(data.error || 'Log in failed'))
    )
  }
}

There's a lot of code in the function above, but take comfort in the fact that the majority of the code is sanitising the response and can be abstracted away.

The first thing we do is dispatch an action LOGIN_REQUEST which updates our store and lets us know that the user isLoggingIn.

dispatch(loginRequest())

We use this to display an activity indicator (spinning wheel, "Loading...", etc.), and to disable the log in button in our log in view.

Next we base64 encode the user's username and password for http basic auth, and pass it to the request's headers.

const hash = new Buffer(`${username}:${password}`).toString('base64')
return fetch('https://httpbin.org/basic-auth/admin/secret', {
  headers: {
    'Authorization': `Basic ${hash}`
  }
/* ... */

If everything went well, we'll dispatch a LOGIN_SUCCESS action, which results in us having an authentication hash in our store, which we'll use in subsequent requests.

dispatch(loginSuccess(hash, data.user))

On the flip side, if something went wrong then we also want to let the user know:

dispatch(loginFailure(data.error || 'Log in failed')

The loginSuccess, loginFailure, and loginRequest action creators are fairly generic and don't really warrant code samples. See: https://github.com/peterp/redux-http-basic-auth-example/blob/master/actions/user.js)

Reducer

Our reducer is also typical:

/// reducers/user.js
function user(state = {
  isLoggingIn: false,
  isAuthenticated: false
}, action) {
  switch(action.type) {
    case LOGIN_REQUEST:
      return {
        isLoggingIn: true, // Show a loading indicator.
        isAuthenticated: false
      }
    case LOGIN_FAILURE:
      return {
        isLoggingIn: false,
        isAuthenticated: false,
        error: action.error
      }
    case LOGIN_SUCCESS:
      return {
        isLoggingIn: false,
        isAuthenticated: true, // Dismiss the login view.
        hash: action.hash, // Used in subsequent API requests.
        user: action.user
      }
    default:
      return state
  }
}

Subsequent API requests

Now that we have an authentication hash in our store we can pass it into subsequent request's headers.

In our example below we're fetching a list of friends for our authenticated user:

/// actions/friends.js
export function fetchFriends() {
  return (dispatch, getState) => {

    dispatch(friendsRequest())

    // Notice how we grab the hash from the store:
    const hash = getState().user.hash
    return fetch(`https://httpbin.org/get/friends/`, {
      headers: {
        'Authorization': `Basic ${hash}`
      }
    })
    .then(response => response.json().then(json => ({ json, response })))
    .then(({json, response}) => {
      if (response.ok === false) {
        return Promise.reject({response, json})
      }
      return json
    })
    .then(
      data => {
        // data = { friends: [ {}, {}, ... ] }
        dispatch(friendsSuccess(data.friends))
      },
      ({response, data}) => {
        dispatch(friendsFailure(data.error))

        // did our request fail because our auth credentials aren't working?
        if (response.status == 401) {
          dispatch(loginFailure(data.error))
        }
      }
    )
  }
}

You may find that most API requests typically dispatch the same 3 actions as above: API_REQUEST, API_SUCCESS, and API_FAILURE, and as such the majority of the request/ response code can be pushed into Redux middleware.

We fetch the hash authentication token from the store and setup the request.

const hash = getState().user.hash
return fetch(`https://httpbin.org/get/friends/`, {
  headers: {
    'Authorization': `Basic ${hash}`
  }
})
/* ... */

If the API response with a 401 status code then we've got to remove our hash from the store, and present the user with a log in view again.

if (response.status == 401) {
  dispatch(loginFailure(data.error))
}

I've answered the question generically and only dealing with http-basic-auth.

I think that the concept may remain the same, you'll push the accessToken and refreshToken in the store, and extract it in subsequent requests.

If the request fails then you'll have to dispatch another action which updates the accessToken, and then recalls the original request.

Sign up to request clarification or add additional context in comments.

9 Comments

I'll finish up the repo that I was working on with a complete example.
(up voted) @peterp this is a great example for basic-auth, but what are your thoughts about persisting as in a "remember me" functionality? Most apps I've seen only need to get your credentials on first launch rather than each launch unless they're finance related (or similar)
I also have a question. Would you recommend to created a middleware to inject the bearer?
@ChrisGeirman I think your recommendation of storing the authentication tokens in something secure like keychain (iOS) and keystore (Android) is probably the best.
@alexmngn Yes I would, I'm actually busy doing that myself, the "how to reduce boilerplate" section in the redux docs goes over this fairly well. search for "Finally, you can write your own middleware" in redux.js.org/docs/recipes/ReducingBoilerplate.html
|
4

I haven't seen too much by way of examples in this area, and think it's definitely something that needs more coverage. I've not yet implemented auth myself, else I'd point you to some code examples. But I can point you to a couple links I've collected that may help you in the right direction...

Regardless how you perform your auth, you'll need to securely store your access, refresh, and secret tokens. On iOS I believe you'd do that using keychain and for Android it looks like KeyStore is the way. You may find oblador/react-native-keychain helpful, though it doesn't yet support android it looks like it may support android soon.

3 Comments

Android is now supported in react-native-keychain!
applause @oblador
Hey @oblador and Chris, I want to use keychain to share pass or any string between 2 distinct react native apps. I am having trouble, do you have any advice? I also have this issue posted github.com/oblador/react-native-keychain/issues/45 . Thanks in advance.
0

I'm actually working on a video tutorial series that answers at least some of the questions your asking. The video along with a transcript and sample code can be found here: http://codecookbook.co/post/how-to-build-a-react-native-login-form-with-redux-pt1/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.