Skip to content

Instantly share code, notes, and snippets.

@martin056
Last active February 1, 2018 16:00
Show Gist options
  • Save martin056/4a4b135ae299d6c1fdbf52f4f563494a to your computer and use it in GitHub Desktop.
Save martin056/4a4b135ae299d6c1fdbf52f4f563494a to your computer and use it in GitHub Desktop.
Implementation of login flow using redux-saga.
function* authenticate(credentials) {
  try {
    const {data} = yield call(requestLogin, credentials);

    storeAuthToken(data.token);

    yield* [
      put(loginSuccess()),
      put(storeMe(data.me)),
      put(push(ROUTER_PROFILE_URL))
    ];
  } catch (error) {
    let errorMessage = '';

    if (error.response) {
      errorMessage = parseResponseErrors(error.response);
    } else {
      errorMessage = [error.toString()];
    }

    yield put(loginError(errorMessage));
  } finally {
    if (yield cancelled()) {
      window.location = '/';
    }
  }
}

function* loginFlow() {
  while (true) {
    let authToken = getAuthToken();
    let authenticateTask = null;

    // If we already have the token we don't want to call authenticate.
    // This happens when a user, that's already logged in, opens a page from the app
    // We still want to run the saga, waiting for logout requests
    if (!authToken) {
      const {credentials} = yield take(LOGIN_REQUEST);
      authenticateTask = yield fork(authenticate, credentials);
    }

    const action = yield take([LOGOUT_REQUEST, LOGIN_ERROR]);

    // If there is an error with the credentials we don't want to run the following logic.
    // But we want to go back listening for login requests.
    if (action.type === LOGIN_ERROR) {
      continue;
    }

    // If the user is trying to log out before logging in is finished we cancel the authentication.
    // This is a very rare race condition that's hard to reproduce, but still, we handle that.
    if (authenticateTask) {
      yield cancel(authenticateTask);
    }

    // If we are logging in for the first time, we won't have authToken
    // That's why we try to get it again.
    if (!authToken) {
      authToken = getAuthToken();
    }

    // If there is a racing condition and the login is not finished but you are trying to logout
    // we won't call `requestLogout` (which will return us 403 Permission Denied) since you can't really
    // log out if you haven't log in.
    if (authToken) {
      removeAuthToken();
      yield call(requestLogout, authToken);

      // We refresh on logout in order to clear everything from the stores, the hard way.
      // This can prevent from some bugs due to improperly unmounted components.
      window.location = '/';
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment