web-dev-qa-db-fra.com

Réaction de l'authentification de base native + redux

Je cherche un moyen de créer une authentification de base pour mon application react-native . Je ne trouvais pas de bon exemple pour cette application.

  • Pour vous connecter, l'application envoie l'email/mot de passe + clientSecret à mon serveur
  • Si OK, le serveur retourne accessToken + refreshToken
  • L'utilisateur est connecté, toutes les autres demandes incluent le porteur avec accessToken.
  • Si accessToken expire, l'application en demande automatiquement un nouveau avec refreshToken.
  • L'utilisateur reste connecté tout le temps, les états doivent être enregistrés dans le téléphone.

Quelle serait la meilleure approche pour cela?

Merci.

24
alexmngn

Lorsqu'une application communique avec une API HTTP qui applique une forme d'authentification, l'application suit généralement les étapes suivantes:

  1. L'application n'est pas authentifiée, nous invitons donc l'utilisateur à se connecter.
  2. L'utilisateur entre ses informations d'identification (nom d'utilisateur et mot de passe) et appuie sur Envoyer.
  3. Nous envoyons ces informations d'identification à l'API et examinons la réponse:
    • En cas de succès (200 - OK): Nous mettons en cache le jeton d'authentification/hash, car nous allons utiliser ce jeton/hash dans chaque demande ultérieure .
      • Si le jeton/hachage ne fonctionne pas lors de l'une des demandes d'API suivantes (401 - non autorisé), nous devons invalider le dièse/jeton et demander à l'utilisateur de se reconnecter.
    • Ou, en cas d'échec (401 - non autorisé): nous affichons un message d'erreur à l'utilisateur, l'invitant à saisir de nouveau ses informations d'identification.

Se connecter

En fonction du flux de travail défini ci-dessus, notre application commence par afficher un formulaire de connexion. Step 2 démarre lorsque l'utilisateur appuie sur le bouton de connexion qui envoie le créateur de l'action login ci-dessous:

/// 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'))
    )
  }
}

La fonction ci-dessus contient beaucoup de code, mais rassurez-vous sur le fait que La majorité du code nettoie la réponse et peut être extraite.

La première chose que nous faisons est d’envoyer une action LOGIN_REQUEST qui met à jour notre magasin et nous informe que l’utilisateur isLoggingIn

dispatch(loginRequest())

Nous l'utilisons pour afficher un indicateur d'activité (rouet, "Chargement ...", etc.) et pour désactiver le bouton de connexion dans notre vue de connexion.

Ensuite, nous encodons en base64 le nom d'utilisateur et le mot de passe de l'utilisateur pour l'authentification de base http, et nous le transmettons aux en-têtes de la requête.

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

Si tout se passe bien, nous enverrons une action LOGIN_SUCCESS, ce qui nous permettra d'avoir une authentification hash dans notre magasin, que nous utiliserons dans les requêtes suivantes.

dispatch(loginSuccess(hash, data.user))

D'un autre côté, si quelque chose ne va pas, nous voulons également informer l'utilisateur:

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

Les créateurs d'action loginSuccess, loginFailure et loginRequest sont assez génériques et ne justifient pas vraiment des exemples de code. Voir: https://github.com/peterp/redux-http-basic-auth-example/blob/master/actions/user.js)

Réducteur

Notre réducteur est aussi typique:

/// 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
  }
}

Requêtes d'API ultérieures

Maintenant que nous avons un hachage d’authentification dans notre magasin, nous pouvons le transférer dans les en-têtes des requêtes suivantes.

Dans notre exemple ci-dessous, nous recherchons une liste d'amis pour notre utilisateur authentifié:

/// 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))
        }
      }
    )
  }
}

Vous constaterez peut-être que la plupart des demandes d'API distribuent généralement les mêmes 3 actions que ci-dessus: API_REQUEST, API_SUCCESS et API_FAILURE et qu'en tant que telles, le code de requête/réponse peut être transmis au middleware Redux.

Nous récupérons le jeton d'authentification par hachage dans le magasin et configurons la demande.

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

Si la réponse de l'API avec un code d'état 401, nous devons alors supprimer notre hachage du magasin et redonner à l'utilisateur une nouvelle vue de connexion.

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

J'ai répondu à la question de manière générique et ne traite que de http-basic-auth. 

Je pense que le concept peut rester le même, vous allez pousser le accessToken et le refreshToken dans le magasin et l'extraire dans les requêtes suivantes.

Si la demande échoue, vous devrez envoyer une autre action mettant à jour le accessToken, puis rappelant la demande initiale.

57
peterp

Je n'ai pas vu beaucoup d'exemples dans ce domaine et je pense que c'est certainement quelque chose qui nécessite une plus grande couverture. Je n'ai pas encore implémenté auth moi-même, sinon je vous indiquerais des exemples de code. Mais je peux vous indiquer quelques liens que j'ai rassemblés qui pourraient vous aider dans la bonne direction ...

Quelle que soit la manière dont vous effectuez votre autorisation, vous devez stocker de manière sécurisée vos jetons d'accès, d'actualisation et de secret. Sur iOS, je pense que vous utiliseriez keychain et pour Android, cela ressemble à KeyStore est le chemin. Vous pouvez trouver oblador/react-native-keychain utile, même s’il ne prend pas encore en charge Android it et qu’il pourrait bientôt prendre en charge Android

4
Chris Geirman

Je travaille actuellement sur une série de didacticiels vidéo qui répond au moins à certaines des questions que vous posez. La vidéo avec une transcription et un exemple de code est disponible à l'adresse suivante: http://codecookbook.co/post/how-to-build-a-react-native-login-form-with-redux-pt1/

0
kmdupr33