Part 1 — Create React App & Rails API Authentication with JWT Tokens and Redux

Christine Tran
9 min readSep 17, 2018

--

Git Repo: https://github.com/christinetran825/SelfCare-C

Git Repo: https://github.com/christinetran825/YourSelfCare-API

Boy, this was a huge issue for me. I had a hard time understanding JWT Tokens. A lot of resources had different takes on how to authenticate and authorize a user. Some used services like Auth0, but I wanted to try sticking to more of an internal setup with fetch. I’m not sure if this is the ‘correct’ way of doing things, but I made it work. Let’s start by defining Authentication and Authorization.

  • Identification: Who you claim to be (email, name, etc)
  • Authentication: The process of verifying you are who you claim to be and assigning your associated role based on your identity
  • Access Policy: Actions the assigned roles are allowed to perform
  • Authorization: Access privileges granted to a user based on their Access Policy

Here’s how it works.

  • Go to a website & submit your email, username, and/or password. These are your credentials
  • The website’s servers verifies (authenticates) if your credentials (identity) are correct.
  • If credentials are correct, the server will issue a cookie to your browser. A common use of cookies is for login. Cookies can also be used to store data in a user’s browsers. That cookie is stored into the browser like an ID badge (Access Policy). This badge allows you to visit other areas of the website without any issues depending on your access privileges (authorization).
  • If your credentials aren’t correct, you’ll get an alert message notifying you that your credentials are incorrect.

Understanding this process we’ll start this post on Identification and Authentication.

Rails API Authentication Set Up

  • Install Gems: gem 'knock' & gem 'jwt'
  • Runbundle install
  • I moved the user_token_controller.rb to my /controllers/api directory. I ensured the code reads the Api directory
class Api::UserTokenController < Knock::AuthTokenController
end
  • In my users_controller.rb, I added another method called find to allow the server to find a user’s by their email after submitting their password.
def find
@user = User.find_by(email: params[:user][:email])
if @user
render json: @user
else
@errors = @user.errors.full_messages
render json: @errors
end
end
  • In my routes.rb, I moved post ‘user_token’ => ‘user_token#create’ to my namespace of :api. I also added a POST to my find method of my users_controller. Here’s the code:
namespace :api do
resources :users, :medications, :insurances
post 'user_token' => 'user_token#create'
post 'find_user' => 'users#find'
end

React API Authentication Set Up — ACTIONS

Per the Redux doc — Actions are payloads of information that send data from your client to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

Per the Redux doc — The Store is the object that brings together actions and reducers. The store has the following responsibilities:

Essentially, Actions pass data from your client to the server through the store. So what are our Actions? Let’s think about the actions a user will take when signing up and logging in through a client. We should also keep in mind of the server’s state, too (this will help us with the Reducers).

Sign Up

  • User creates an account by entering their credentials and clicks submit.
  • The client (web browser) communicates with the server (Rails API) by sending those credentials. Here the client is Requesting Authentication (Action) and the server is Authenticating (state) a user to determine if their credentials meet the validation rules (like meeting the character limit for a password).
  • If the credentials don’t meet the validation rules, the client is sent an Authentication Failure (Action) and the server has an error (state) so the user can correct their credentials.
  • If the server determines the validation is correct, the client is sent an Authentication Success (Action) and the server has Authenticated (state) the user. The user is then given a token in the server. That token is another form of identification. Once the token is provided, the user is now the current user and is redirected to the next webpage according to the Rails API Routes.

Log In

  • User enters their credentials and clicks submit.
  • The client (web browser) communicates with the server (Rails API) by sending those credentials to be verified. Here the client is Requesting Authentication (Action) and the server is Authenticating the user by finding them in the User database based on their email or whatever params the User Controller in the API defined.
  • If the credentials are not correct, the client is sent an Authentication Failure (Action) and the server has an error (state) so the user can correct their credentials.
  • If the credentials are correct, the token is sent to the client notifying it that there was an Authentication Success (Action) and the server has Authenticated the user. The user’s token allows the user to be the current user and they are redirected to the next webpage according to the Rails API Routes.

Based on our understanding of how a user signs up and logs in through a client (web browser), the user experiences the following action types:

  • Authentication Request
  • Authentication Success
  • Authentication Failure

I defined these action types in my actionTypes.js file that will be imported and referenced in the authActions.js file with a types. before the type name.

//Authentication Typesexport const AUTHENTICATION_REQUEST = "AUTHENTICATION_REQUEST"
export const AUTHENTICATION_SUCCESS = "AUTHENTICATION_SUCCESS"
export const AUTHENTICATION_FAILURE = "AUTHENTICATION_FAILURE"
export const LOGOUT = "LOGOUT"

Each of these actions were also passed or received either credentials, a token, or errors. These things are our objects. The credentials when correct helps us find our user. We defined these action types with a constant function.

import fetch from 'isomorphic-fetch';
import { API_URL } from './apiUrl'
import * as types from './actionTypes'
//this function has an authentication request action typeconst authRequest = () => {
return {
type: types.AUTHENTICATION_REQUEST
}
}
//this function has an authentication success action type. When there's a success in correct credentials, the server passes a user and token.const authSuccess = (user, token) => {
return {
type: types.AUTHENTICATION_SUCCESS,
user: user,
token: token
}
}
//this function has an authentication failure action type. When there are incorrect credentials, the server passes errors.const authFailure = (errors) => {
return {
type: types.AUTHENTICATION_FAILURE,
errors: errors
}
}

These action types can now be referenced into our actions that will be called in our handleSubmit of our Sign Up and Log In forms along with our Log Out button. So let’s define these actions.

Fetch

Note: I highly recommend reading through the Fetch MDN to understand how it works — https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

If Actions are payloads of information that send data from your client to your store, then where do our data come from? It comes from our Rails API database. So each Action must fetch data from our Rails API database routes.

Fetch sets a parameter to the path to the resource you want to fetch with, in this case it ishttps://localhost:3001/api with the routes append to the end. The fetch then returns a promise containing the response (an HTTP Response object). We want our data to be converted into JSON, so to get the JSON body content from the response, we use the json() method. Fetch can also optionally set a second parameter, which is an init object that allows us to POST JSON-encoded data.

To make the code DRY, I added a .env file in my root directory with a constant leading to the resource.

REACT_APP_API_URL=http://localhost:3001/api

In my src/actions directory I add the file apiURL.js the will process the .env file. It can now be referenced in all my Actions through the const API_URL.

export const API_URL = process.env.REACT_APP_API_URL;

Defining the Actions — Signing Up a user

signup action:

  • the client passes a user object to the server. We define this object in a const newUser
  • we fetch the /users resource and pass in our init object
  • We define the init object with a method of "POST" since we’re creating a user. The headers denote that we are accepting json data and the content-type should be json data. Next we define our body as a JSON object by passing in our user object.
  • THEN, we’ll receive a response and we convert that response into json
  • THEN, we take that converted json response that now includes our user object and dispatch it to our authenticate action. We will authenticate that user by defining their name, email, and password to the params.
  • IF the authentication in the authenticate action can’t be completed, errors object will be returned and dispatched to the authFailure action type.
export const signup = (user) => {
const newUser = user
return dispatch => {
return fetch(`${API_URL}/users`, {
method: "POST",
headers: {
"Accept":"application/json",
"Content-Type":"application/json"
},
body: JSON.stringify({user: user})
})
.then(response => response.json())
.then(jresp => {
dispatch(authenticate({
name: newUser.name,
email: newUser.email,
password: newUser.password})
);
})
.catch((errors) => {
dispatch(authFailure(errors))
})
};
}

authenticate action:

  • the client passes the user’s credentials and dispatches the action type of authRequest() as we’re making a request to authenticate the credentials
  • at this point, we are defining or creating a user’s token. we’ll fetch our /user_token resource and pass in our init object
  • We define the init object with a method of "POST" since we’re creating a token. The headers denote that the content-type should be json data. Next we define our body as a JSON object by passing in the user’s credentials to be authenticated
  • THEN, we’ll receive a response and we convert that response into json
  • THEN, we take that converted json response and convert it as a jwt token. We’ll define this token into const token. The token object will now be stored or .setItem into the localStorage. The credentials that the response held onto will be passed into our getUser action. The action will find the user associated to the token that was stored.
  • IF the user in the getUser action can be found, the user object that was carried over from the signup action will be passed to the authSuccess action type along with the newly stored token localStorage.token.
  • IF the user in the getUser action can’t be found, errors object will be returned and dispatched to the authFailure action type. The localStorage will clear itself since no tokens were passed.
export const authenticate = (credentials) => {
return dispatch => {
dispatch(authRequest())
return fetch(`${API_URL}/user_token`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({auth: credentials})
})
.then(res => res.json())
.then((response) => {
const token = response.jwt;
localStorage.setItem('token', token);
return getUser(credentials)
})
.then((user) => {
console.log(user)
dispatch(authSuccess(user, localStorage.token))
})
.catch((errors) => {
dispatch(authFailure(errors))
localStorage.clear()
})
}
}

getUser action:

  • the credentials were passed from authenticate action
  • we define a const request to point to a new Request to our resource of /find_user and pass in our init object
  • we define the init object with a method of "POST" since we’re creating a user. The headers denote that the content-type should be json data along with an authorization where we’ll associate the token object to a Bearer. Next we define our body as a JSON object by passing in the user’s credentials
  • now, we make a fetch to the defined const request
  • THEN, we’ll receive a response and we convert that response into json
  • THEN, we take that converted json response and return it as our user object. This user object is passed back to authenticate action and is dispatched to the authSuccess action type passing in the user object and its token.
  • IF the user can’t be found, then an error object is returned.
export const getUser = (credentials) => {
const request = new Request(`${API_URL}/find_user`, {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
"Authorization": `Bearer ${localStorage.token}`,
}),
body: JSON.stringify({user: credentials})
})
return fetch(request)
.then(response => response.json())
.then(userJson => {return userJson})
.catch(error => {
return error;
});
}

WHOA… That’s a lot to define when signing up a user. What happens when we log in a user? Luckily, we already defined all the actions needed for it.

Defining the Actions — Logging In a user

Since a user that wants to log in through a client, they’ve already have a token associated with their credentials. All that needs to be done is have the client pass their credentials into the authenticate action. The cycle continues as before. When the credentials get passed into the getUser action, the request will go to the resource /find_user to find the user based on their credentials and pass their existing token back to the authenticate action where it and the user will be dispatched to authSuccess action type.

Defining the Actions — Logging Out a user

We simply dispatch a call to clear the localStorage of the token that was passed to client to the LOGOUT action type.

export const logout = () => {
return dispatch => {
localStorage.clear();
return dispatch({
type: types.LOGOUT
});
}
}

Referencing the actions in our components

SignUp component

handleSubmit = (e) => {
e.preventDefault();
if (this.props.signup(this.state)) {
this.props.history.push('/user_profile')
window.alert("Thank you for signing up.")
} else {
window.alert("We're having issues creating your account.")
}
}

LogIn component

handleSubmit = (e) => {
e.preventDefault();
if (this.props.authenticate(this.state)) {
this.props.history.push('/user_profile')
window.alert("You're Logged In!")
} else {
window.alert("Sorry, something went wrong. Please try logging in again.")
}
}

Navigation component

handleLogout = (e) => {
e.preventDefault();
this.props.logout();
this.props.history.push('/')
}

WHOA….. that was A LOT to take in. I hope you have a better understanding about the Rails API setup and the React ACTIONS setup. Take a moment to digest what you’ve learned and when you’re ready, we’ll continue with Reducers, Access Policy, and Authorization in Part 2.

Resources:

--

--