OAuth in React/Redux Applications

Michael Ries
5 min readSep 1, 2017

--

2017–07–19 13:59:54 -0700

For my final project in the Flatiron School Full Stack Web Developer Program, I chose to create a single-page app using React and Redux. I wrote a basic back end API using Rails to provide the data storage and retrieval, and then proceeded with the front end. Creating the API was fairly straightforward and won’t be discussed in detail here.

The nature of the app — storing and retrieving SCUBA dive data — requires some sort of authentication so that you can trust that your data haven’t been tampered with. In addition, I detest being required to create new accounts for every web app that I visit, so I wanted to use OAuth2. This seems like a common scenario, so I thought for sure there would be some third-party library that would make it easy.

I searched and found a couple that were promising: redux-auth and react-native-oauth, but they seemed unneccessarily complicated. I suppose part of this was that I didn’t understand JWT as well as I should.

After reading many blogs and tutorials I finally decided that it isn’t really all that hard to do myself. The flow looks something like the following diagram, using Facebook as an OAuth provider for example:

Ok, so right now you’re probably thinking that really doesn’t look simple. But most of the complexity is contained in the boxes with the red text, which handled without much fuss by the Omniauth gem or by devise. Admittedly, devise isn’t exactly a walk in the park to use, but it’s popular enough you may have already run into it. Personally, devise is overkill for this project, as I only wanted OAuth and had no need for password resets, 2-factor authentication, etc, so I went with the omniauth gem.

1. Starting the process

This is as simple as providing a button for the user to click, indicating they want to log in. I chose to support login via Facebook or Github, but you could use any OAuth service that you choose, provided that it is supported by omniauth. Each button calls the #authenticate action in the SessionsController, which redirects to the appropriate omniauth authentication path.

def authenticate
redirect_to '/auth/facebook' if params[:type] == 'facebook'
redirect_to '/auth/github' if params[:type] == 'github'
end

2. Authenticate the User with OmniAuth

The API authenticates the user using the omniauth gem. I won’t go into detail on this, as the gem is well documented here.

3. Find or Create User

The API takes the result from the omniauth callback to find or create a user. In the SessionsController we have:

def create
user = User.find_or_create_by(uid: auth['uid']) do |u|
u.name = auth['info']['name']
u.email = auth['info']['email']
u.image = auth['info']['image']
end
... enddef auth
request.env['omniauth.auth']
end

That’s all we need to authenticate the user and find their ID.

NOTE: If you are mixing OAuth login with ‘traditional’ username and passwords, you should create a random password for accounts created via OAuth. Otherwise they won’t have one!

Next we must send the data back to the client in the form of a JWT.

4. Generate a JWT and sent it to the Client

A JWT is a JSON Web Token which is an industry standard method for passing data securely in a digitally signed way. To create this JWT, the API combines the data, the header, and a secret key and mixes it all up into a secure package that can be transmitted to the client.

There are lots of resources out there on how to create and use JWTs:

My favorite was actually written by a Flatiron Student/Instructor:

On the server side, I used the jwt gem to encode and decode the tokens. I wrote a helper class to do the dirty work:

class Auth
def self.encode_uid(user_id)
payload = { user_id: user_id }
JWT.encode payload, ENV['AUTH_SECRET'], 'HS256'
end

def self.decode_uid(token)
payload = JWT.decode token, ENV['AUTH_SECRET'], true,
{ :algorithm => 'HS256' }
payload[0]['user_id']
end
end

Note that we store the secret in an environment variable so we don’t accidentally upload it to github!

Now that we have created the Auth class, returning the user ID back to the client inside a JWT is easy. In our SessionsController, the create action now becomes this:

def create
user = User.find_or_create_by(uid: auth['uid']) do |u|
u.name = auth['info']['name']
u.email = auth['info']['email']
u.image = auth['info']['image']
end
if user
jwt = Auth.encode_uid(user.uid)
redirect_to(ENV['DIVE_LOG_CLIENT_URL'] + "?token=#{jwt}")
end
end

NOTE: You will be sending the JWT to the client in the query string, which is plain text. Anyone who gets your JWT can use it to gain access to the API and your data. To avoid this you must use an https endpoint on the client which will encrypt the query string.

5. Use of the JWT by the client.

On the client side, the JWT is stored in session storage:

sessionStorage.setItem(‘jwt’, jwt)

Then all the client needs to do is put it in the header for each request it makes to the api. For example, a get method looks like this:

get(url) {  
const jwt = sessionStorage.getItem('jwt');
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer: ${jwt}`
}
return fetch(`${API_URL}${url}`, {
method: 'GET',
headers: headers
}).then(response => (response.json()));
}

6. Using the JWT to identify the current user

In the application controller, we have some helper methods that perform the authentication and extract the current_user:

class ApplicationController < ActionController::API  
before_action :authenticate
def logged_in?
!!current_user
end
def current_user
return @current_user if @current_user
if auth_present?
uid = Auth.decode_uid(read_token_from_request)
@current_user = User.find_by({uid: uid})
return @current_user if @current_user
end
end
def authenticate
render json: {error: "unauthorized"},
status: 401 unless logged_in?
end
private def read_token_from_request
token = request.env["HTTP_AUTHORIZATION"]
.scan(/Bearer: (.*)$/).flatten.last
end
def auth_present?
!!request.env.fetch("HTTP_AUTHORIZATION", "")
.scan(/Bearer/).flatten.first
end
end

Summary

This is a fairly simple implementation of OAuth for a react app with a rails backend. It’s not without some drawbacks however:

  • The redirect from the API back to the Client causes a complete page reload. This probably isn’t something to worry about, but keep it in mind.
  • Although HTTPS is increasingly common, it may not be available to you. I am looking into possible improvements. One idea would be to randomly generate a ‘client secret’ and POST that to the API during authentication. Then the API would use both it’s own secret and the client secret in the formation of the JWT. With this change, the JWT alone would not be enough to gain access, you’d also need the client secret.

Finally, this work is being done as part of my learning process and I am certainly no expert in security. If you see things that need correction, or if you have additional resources for me, please do not hesitate to contact me.

Originally published at michaelries.info.

--

--

Michael Ries

Solving complex problems one simple problem at a time.