4

After a bit of research, JWT is commonly used for login authentication because of its compact nature and easiness to parse. I have settled on using JWT. However, my question is on how to embed this in my redux paradigm. Assuming we have a sign up form, when a user fills in his or her credentials and clicks a submit button, this will invoke an action to create an action to create a JWT. Now, this action goes to the back-end of my application and the back-end of my application calls the JWT API? So this action is an asynchronous/rpc call? Also, how does routing happen exactly? I have used react-router before, but using a boilerplate. I am building this web app from scratch and so I am a bit confused on where to deal with the routing and where do I pass this token exactly that I obtain from the server the first time? Is the token used every time a user does a request? How does the client know about this token every time it does the request so that it would keep a user authenticated?

3 Answers 3

3

When a user submits his credentials (email/password) your backend authenticates that for the first time and only this time does the backend use these credentials. On authentication your backend will create a JWT with some of the user information, usually just the user ID. There are plenty of JWT Libraries and even jwt-decode for javascript to do this. The backend will respond with this JWT where the front-end will save it (ie, localStorage.setItem('authToken', jwt)) for every subsequent request.

The user will send a request with the JWT in the request header under the Authorization key. Something like:

function buildHeaders() {
    const token = localStorage.getItem('authToken')

    return {
      "Accept": "application/json",
      "Content-Type": "application/json"
      "Authorization": `${token}`
    }
}

Your backend will now decode and authenticate the JWT. If it's a valid JWT the request continues, if not it's rejected.

Now with React-Router you can protect authenticated routes with the onEnter function. The function you provide does any necessary checks (check localStorage for JWT and if a current user). Typically I've done this:

const _ensureAuthenticated = (nextState, replace) => {
    const { dispatch } = store
    const { session } = store.getState()
    const { currentUser } = session
    const token = localStorage.getItem("phoenixAuthToken")
    if (!currentUser && token) {     // if no user but token exist, still verify
      dispatch(Actions.currentUser())
    } else if (!token) {             // if no token at all redirect to sign-in
      replace({
        pathname: "/sign-in",
        state: { nextPathname: nextState.location.pathname}
      })
    }
  }

You can use this function in any route like so:

<Route path="/secret-path" onEnter={_ensureAuthenticated} />

Check out jwt.io for more information on JWT's and the react-router auth-flow example for more information on authentication with react-router.

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

Comments

3

I personally use Redux saga for async API calls, and I'll show You the flow I've been using for JWT authorization:

  1. Dispatch LOG_IN action with username and password
  2. In your saga You dispatch LOGGING_IN_PROGRESS action to show e.x. spinner
  3. Make API call
  4. Retrieved token save e.x. in localstorage
  5. Dispatch LOG_IN_SUCCESS or LOG_IN_FAILED to inform application what response did You get

Now, I always used a separate function to handle all my requests, which looks like this:

import request from 'axios';
import {get} from './persist'; // function to get something from localstorage

export const GET = 'GET';
export const POST = 'POST';
export const PUT = 'PUT';
export const DELETE = 'DELETE';

const service = (requestType, url, data = {}, config = {}) => {
    request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';
    switch (requestType) {
        case GET: {
            return request.get(url, data, config);
        }
        case POST: {
            return request.post(url, data, config);
        }
        case PUT: {
            return request.put(url, data, config);
        }
        case DELETE: {
            return request.delete(url, data, config);
        }
        default: {
            throw new TypeError('No valid request type provided');
        }
    }
};
export default service;

Thanks to this service, I can easily set request data for every API call from my app (can be setting locale also).

The most interesting part of it should be this line:

request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';`

It sets JWT token on every request or leave the field blank.

If the Token is outdated or is invalid, Your backend API should return a response with 401 status code on any API call. Then, in the saga catch block, you can handle this error any way You want.

Comments

0

I recently had to implement registration and login with React & Redux as well.

Below are a few of the main snippets that implement the login functionality and setting of the http auth header.

This is my login async action creator function:

function login(username, password) {
    return dispatch => {
        dispatch(request({ username }));

        userService.login(username, password)
            .then(
                user => { 
                    dispatch(success(user));
                    history.push('/');
                },
                error => {
                    dispatch(failure(error));
                    dispatch(alertActions.error(error));
                }
            );
    };

    function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
    function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
    function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}

This is the login function of the user service that handles the api call:

function login(username, password) {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    };

    return fetch('/users/authenticate', requestOptions)
        .then(response => {
            if (!response.ok) { 
                return Promise.reject(response.statusText);
            }

            return response.json();
        })
        .then(user => {
            // login successful if there's a jwt token in the response
            if (user && user.token) {
                // store user details and jwt token in local storage to keep user logged in between page refreshes
                localStorage.setItem('user', JSON.stringify(user));
            }

            return user;
        });
}

And this is a helper function used to set the Authorization header for http requests:

export function authHeader() {
    // return authorization header with jwt token
    let user = JSON.parse(localStorage.getItem('user'));

    if (user && user.token) {
        return { 'Authorization': 'Bearer ' + user.token };
    } else {
        return {};
    }
}

For the full example and working demo you can go to this blog post

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.