Build a simple Rails API server + Auth0 JWT authentication + React from scratch in 30 minutes (or less)


TL;DR In a nutshell the following gif (it might take a minute to load) shows what this article is all about:

React + Auth0 + Rails API demo
Demo app is located at http://react-auth0-api.herokuapp.com/
Note : If you access the site for the first time, it might take about 30 seconds to ping the API server as heroku needs to start up the app.
Github-Rails API server : https://github.com/iankhor/ror-auth0-api
Github-React Front-end : https://github.com/iankhor/react-auth0-api

A mate of mine, Jason H — a great coder himself, got me thinking about authenticated API calls from a browser. So I thought to give it a crack myself !

Purpose

The intention of this guide is for newcomers or startups who wants to quickly bring up a platform to get users. It does not follow any general best practices but serves as a guide to get an API server with a front-end up and running up quickly. This article is not the only way to do things but one of the ways to get some traction on your current project. If you run into any issues, I’d be happy to assist but Google is always your best companion

Disclaimer

If you are reading this 1 or 2 years from the time of this article was published, it is highly likely that the following steps could be outdated. I will try to update it as often as I could so tread with caution !


The Very Simple Plan

We will start with a Rails API using Auth0 and Postman to send GET & POST requests to fetch authenticated data.

Auth0 handles the sign in/up process. It authenticates a user and supplies a JSON Web Token (JWT) that is will be sent in the header of a request to the API server.

The following simplified diagram below illustrates on a high level how the Rails API server will function initially without a front-end.

Green — START of process. Red — end of process.

We will enhance the plan above by using a front-end framework. In this article we will be using React, however you can use any other framework to perform a similar logic.

The Rails API server is able to standalone without any dependancies on what front-end framework is being used.

The Next Simple Plan

Once we are confident that the Rails API server is functioning, we will use a front-end framework to perform the functionality of Postman as illustrated in a simplified diagram below:

Green — START of process. Red — end of process.

So lets begin !


BACK END — Rails API Server

Prerequisites, tools and items needed

  1. Rails 5.0.0
  2. Ruby 2.3.1
  3. Auth0 client ID
  4. Auth0 client secret
  5. Auth0 domain ID
  6. Auth0 dummy username and password (set one up after you have signed up with Auth0, this article assumes you have one set up with the username admin@admin.com and password admin)
  7. Ruby gem rack-cors
  8. Ruby gem bcrypt 3.1.11
  9. Ruby gem knock 2.1.1(to handle jwt from Auth0)
  10. Ruby gem active_model_serializer v.0.10.5
  11. Ruby gem dotenv-rails v2.2.0 (for development only)
  12. Ruby gem faker v1.7.3 (optional and used to generate fake data)
  13. Ruby gem pg v0.20.0 (for postgres database)
  14. Ruby gem rack-cors v0.4.1 (to handle cross-origin requests)
  15. Postman (optional and used to test APIs)
  16. Postgresapp database server (this will serve as our local database server)
  17. Rails API server will run on localhost:3000

Steps

The following steps assumes you have Ruby, Rails, Postgresapp, Postman installed in your system. Make sure you have signed up for a Auth0 account and have a client ID, domain and client secret. If you are still unsure what they are, read the Auth0 setup section of my other article here to get a feel how Auth0 works.

  1. Create a Rails API app, run rails new ror-auth0-api --api --database=postgresql on your terminal
  2. Create an environment variables file called .env in the root level of your app and add the following information from Auth0
AUTH0_CLIENT_ID={CLIENT_ID}
AUTH0_DOMAIN={DOMAIN}
AUTH0_CLIENT_SECRET={CLIENT_SECRET}

3. We need to tell rails to use the Auth0 credentials. Update the secrets.yml file in config folder to look like below:

development:
secret_key_base: ...YOUR KEY GENERATED BY RAILS...
auth0_client_id: <%= ENV["AUTH0_CLIENT_ID"] %>
auth0_client_secret: <%= ENV["AUTH0_CLIENT_SECRET"] %>
test:
secret_key_base: ...YOUR KEY GENERATED BY RAILS...
auth0_client_id: <%= ENV["AUTH0_CLIENT_ID"] %>
auth0_client_secret: <%= ENV["AUTH0_CLIENT_SECRET"] %>
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
auth0_client_id: <%= ENV["AUTH0_CLIENT_ID"] %>
auth0_client_secret: <%= ENV["AUTH0_CLIENT_SECRET"] %>

4. Add the following gems to your Gemfile :

gem 'bcrypt', '~> 3.1.7'
gem 'dotenv-rails', groups: [:development, :test]
gem 'faker', '~> 1.7.2'
gem 'rack-cors','~>0.4.1'
gem 'active_model_serializers', '~> 0.10.0'
gem 'knock', '~> 2.0'

5. Run bundle install

6. Create a user model, run rails g model User name email password_digest

7. Create and migrate your postgres database, run rails db:create && rails db:migrate

8. In the User model , add the following code snippet:

# app/models/user.rb
class User < ApplicationRecord
has_secure_password
  def self.from_token_payload payload
payload['sub']
end
end

9. Install the gem knock by running rails g knock:install

10. In application controller, add the following code snippet to include theknock gem. This updates knock's default error message when a user tries to fetch data from a controller that requires authentication.

# app/controllers/application_controller.rb

class ApplicationController < ActionController::API
include Knock::Authenticable
  private
  # Define unauthorized access json response
def unauthorized_entity(entity_name)
render json: { error: "Unauthorized request" }, status:
:unauthorized
end
end

11. Now we will create two controllers called home and profile. The home controller will be used to ping the API server without a JWT, whereas the profiles controller would require a valid JWT to fetch data.

12. Run rails g controller Home

13. Runrails g scaffold profile first_name:string last_name:string middle_name:string username:string email:string address:string phone:string profession:string abn:string

14. Run rails db:migrate

13. In your home controller, add the following code snippet. The API server will respond with a simple message when the API server is called.

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
render json: { message: "Welcome to a simple API server with
Auth0"}
end
end

14. In your profiles controller , add before_action :authenticate_user to the controller. Any API calls to profiles will require authentication.

# app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
...
before_action :authenticate_user
# GET /profiles
def index
...
end
  ....
end

16. To enable cross-origin requests, uncomment the following block of code in config/initializers/cors.rb . Ensure that any origin is allowed in this case. (ie : origins '*' )

#config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end

17. We need to configure knock to use Auth0 credentials. Uncomment the following code in config/initializers/knock.rb

# config/initializers/knock.rb
config.token_audience = -> { Rails.application.secrets.auth0_client_id }
# DO NOT USE THE DEFAULT CONFIG 
# config.token_secret_signature_key = -> { JWT.base64url_decode Rails.application.secrets.auth0_client_secret }
# USE THIS INSTEAD
config.token_secret_signature_key = -> { Rails.application.secrets.auth0_client_secret }
Note : knock assumes Auth0’s client secret is base64 encoded but as of writing it isn’t. Therefore we need do not need to decode it as shown in the comment code block above.

18. We need to configure our routes. Ensure in the config/routes.rb has the following code snippet

#config/routes.rb   
Rails.application.routes.draw do
resources :profiles
root 'home#index'
end

19. Lastly, lets add some fake data to simulate a list of profiles. In your db/seeds.rb file, add the following code snippet.

#db/seeds.rb
10.times do |i|
first_name = Faker::Name.first_name
last_name = Faker::Name.last_name
middle_name = Faker::Name.name_with_middle
username = Faker::Internet.user_name(first_name + " " + last_name, %w(. _ -))
email = Faker::Internet.free_email(first_name + "-" + last_name)
phone = Faker::PhoneNumber.cell_phone
profession = Faker::Company.profession
abn = Faker::Company.australian_business_number
Profile.create!(
first_name: first_name,
last_name: last_name,
middle_name: middle_name,
username: username ,
email: email,
phone: phone,
profession: profession,
abn: abn,
)
end

20. Run rails db:seed to populate our profiles database

21. Now we are done setting up the Rails server ! Lets fire it up and see if its working. Ensure postgresapp is running in the background. Start the rails API server by runingrails s.

22. Now, we need to get a JSON Web Token (JWT) from Auth0. Start Postman, and send a POST request to https://_your_auth0_domain_.com/oauth/ro with the following in the body as JSON

{
"client_id": "___YOUR AUTH0 CLIENT ID___",
"username": "admin@admin.com",
"password": "admin",
"connection": "___YOUR AUTH0 DATABASE___",
"scope": "openid"
}

23. You should get a response with id_token. That will serve as your JWT for authentication next. Copy it down.

Copy id_token to be used in the next step

24. Assuming your Rails API server is running on localhost:3000, use Postman again to send a GET request to localhost:3000 .You should receive a response similar to the figure below:

You should receive a response as shown in the above figure

25. Now lets test an authenticated route — the profiles controller. In Postman, send a GET request to localhost:3000/profiles with Authorization : Bearer __your id_token in step 23__ . You should receive a response similar to the figure below:

26. That’s it ! We now have a simple API server up and running !

Next, we will use React as our front-end framework to replace the functionality of Postman from step 22 to 26.


FRONT END — React

Prerequisites, tools and items needed

  1. My react boilerplate : react-vanilla-boilerplate-with-router
  2. Axios (to perform fetch request to the Rails API server)
  3. React Bootstrap 4 — reactstrap
  4. Font Awesome
  5. React front-end to run on localhost:9000

Steps

  1. In your terminal, run git clone https://github.com/iankhor/react-vanilla-boilerplate-with-router react-auth0-api
  2. Remove existing git repository and remotes from Step 1 by running `git remote rm origin && rm -rf .git .You can add your own git repository from here on if you desired.
  3. In your root directory of react-auth0-api folder, run cp .env.example .env
  4. Ensure in the .env file, the following environment variables are populated. An example would be :
REACT_APP_API_URL=http://localhost:3000
REACT_APP_AUTH0_CLIENT_ID=___YOUR AUTH0 CLIENT ID___
REACT_APP_AUTH0_DOMAIN=___YOUR AUTH0 DOMAIN____

4. Install all packages required by the boilerplate by running npm i

5. Now lets install all the other packages. Run the following:

npm i auth0-lock --save
npm i axios --save
npm i bootstrap@4.0.0-alpha.6 --save
npm i --save reactstrap react-addons-transition-group react-addons-css-transition-group

6. We will use a spinner icon to simulate a loading component using icons from Font Awesome. Get the CDN html link from https://www.bootstrapcdn.com/fontawesome/ and paste the code snippet between the <head> tag in the public/index.html file.

8. To ensure bootstrap CSS is carried over, ensure the bootstrap.css is imported in src/components/index.js

#src/components/index.js
import 'bootstrap/dist/css/bootstrap.css'

7. We will use Auth0’s own Lock widget (auth0-lock) to sign in/up users. In the public/index.html file, ensure the following code snippet is between the <head> tags to ensure the Auth0 Lock widget will work on mobile devices

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>

8. Your public/index.html file should look something like this now:

9. In src/utils, create a file called init.js and add the following code snippet. This is the initialisation methods for Auth0 and Axios.

import AuthService from './AuthService'
import axios from 'axios'
export const auth = new AuthService(
process.env.REACT_APP_AUTH0_CLIENT_ID,
process.env.REACT_APP_AUTH0_DOMAIN)
export const api = axios.create({
baseURL: process.env.REACT_APP_API_URL
})

10. In src/utils, create a file called AuthService.js and add the following code snippet. These are helper methods to use Auth0.

import Auth0Lock from 'auth0-lock'
import logo from './../../assets/img/logo.svg'
export default class AuthService {
constructor(clientId, domain) {
// Configure Auth0
this.lock = new Auth0Lock(clientId, domain, {
auth: {
redirectUrl: `${window.location.origin}/auth`,
responseType: 'token'
},
theme: {
logo: logo,
primaryColor: '#7FDBFF'
},
languageDictionary: {
title: "React + Auth0 + Rails API"
}
})
// Add callback for lock `authenticated` event
this.lock.on('authenticated', this._doAuthentication.bind(this))
// binds login functions to keep this context
this.login = this.login.bind(this)
}
_doAuthentication(authResult) {
// Saves the user token
this.setToken(authResult.idToken)
// navigate to the home route
    location.replace("/");
}
login() {
// Call the show method to display the widget.
this.lock.show()
}
loggedIn() {
// Checks if there is a saved token and it's still valid
return !!this.getToken()
}
setToken(idToken) {
// Saves user token to local storage
localStorage.setItem('id_token', idToken)
}
getToken() {
// Retrieves the user token from local storage
return localStorage.getItem('id_token')
}
logout() {
// Clear user token and profile data from local storage
localStorage.removeItem('id_token');
    location.replace("/");
}
}

11. In src/utils, create a file called API.js and add the following code snippet. These are helper methods to fetch and ping the Rails API server.

import { api } from './init'
export function pingApiServer(){
return api.get('/')
.then(function (response) {
return response.data
})
.catch(function (error) {
return error.response.data
})
}
export function fetchProfilesNoAuth(){
return api.get('/profiles')
.then(function (response) {
return response.data
})
.catch(function (error) {
return error.response.data
})
}
export function fetchProfilesWithAuth(token){
return api.get('/profiles', { headers: { 'Authorization': 'Bearer ' + token } })
.then(function (response) {
return response.data
})
.catch(function (error) {
return error.response.data
})
}

12. Update App.js in src/components with the following code snippet

import React, { Component } from 'react';
import logo from '../../assets/img/logo.svg'
import '../css/style.css'
import Home from './Home'
import { auth } from './../utils/init'
class App extends Component {
constructor(props){
super(props)
this.state = {
isLoggedIn: auth.loggedIn(),
}
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React + Auth0 + Rails API</h2>
</div>
<Home 
auth={auth}
isLoggedIn={ this.state.isLoggedIn }
token={auth.getToken()}
/>
</div>
);
}
}
export default App;

13. Create three components files in src/components and named them Home.js , LoginTransition.js and Loading.js

14. Paste the following code snippet to Home.js

//src/components/Home.js
import React, { Component } from 'react'
import JSONDebugger from '../utils/JSONDebugger'
import { pingApiServer,
fetchProfilesWithAuth,
fetchProfilesNoAuth } from './../utils/API'
import { Container,
Row,
Col,
Button,
ButtonGroup } from 'reactstrap'
import Loading from './Loading'
class Home extends Component {
constructor(props){
super(props)
this.state = {  status: "Idle",
pingData: "No data from server yet",
profileData: "No data from server yet"
}
}
resetStates = () => {
this.setState({
status: "Idle",
pingData: "No data from server yet",
profileData: "No data from server yet"
})
}
renderLoading = () => {
switch(this.state.status) {
case "Fetching":
return <Loading />
default:
return null
}
}
pingApi = () => {
this.setState( { status: "Fetching" })
pingApiServer()
.then( data => {
this.setState({ status: "Fetch completed", pingData: data} )
} )
}

_fetchProfilesNoAuth = () => {
this.setState( { status: "Fetching" })
fetchProfilesNoAuth()
.then( data => {
this.setState({ status: "Fetch completed", profileData: data} )
})
}
_fetchProfilesWithAuth = () => {
this.setState( { status: "Fetching" })
fetchProfilesWithAuth(this.props.token)
.then( data => {
this.setState({ status: "Fetch completed", profileData: data} )
})
}
renderSignInUp = () => {
return <Button color="primary" onClick={ this.props.auth.login }>Sign In / Sign Up</Button>
}
renderLogOut = () => {
return (
<div>
<Row>
<Col>
<Button color="primary" onClick={ this.props.auth.logout}>Sign out</Button>
<Button color="secondary" onClick={ this.pingApi }>Ping API Server</Button>
</Col>
</Row>
<Row>
<Col xs="12" sm="6">
<Button color="danger" onClick={this._fetchProfilesNoAuth }>Fetch Profile without Authentication</Button>
</Col>
<Col xs="12" sm="6">
<Button color="success" onClick={ this._fetchProfilesWithAuth }>Fetch Profile with Authentication</Button>
</Col>
</Row>
<Row>
<Col>
<Button outline color="danger" size="sm" onClick={ this.resetStates }>Reset</Button>
</Col>
</Row>
</div>
)
}
render(){
return(
<Container>
<div className="buttons">
<ButtonGroup vertical>
{ this.props.isLoggedIn ? this.renderLogOut() : this.renderSignInUp() }
</ButtonGroup>
</div>
{ this.renderLoading() }
{ <JSONDebugger json={this.state} /> }
</Container>
)
}
}
export default Home

15. Paste the following code snippet to LoginTransition.js

//src/components/LoginTransition.js
import React, { Component } from 'react'
import { Container, Row, Col } from 'reactstrap'
import Loading from './Loading'
class LoginTransition extends Component {
render(){
return(
<Container className="login-transition">
<Row>
<Col>
<Loading />
</Col>
</Row>
</Container>

)
}
}
export default LoginTransition

16. Paste the following code snippet to Loading.js

//src/components/Loading.js
import React from 'react'
const Loading = (props) => {
return (
<div>
<i className='fa fa-spinner fa-spin fa-5x color-font-aqua'></i>
</div>
)
}
export default Loading

17. We will also need to update some of our CSS as well. In src/css/style.styl . Replace the existing CSS entires the following code snippet. Do not update the style.css file. The scripts in the boilerplate will look after that.

//src/css/style.styl
h1
font-size 2em
.color-silver
background-color #DDDDDD
.color-font-aqua
color #7FDBFF
.color-aqua
background-color #7FDBFF
.generic-center
margin auto
padding 2rem
border 2px solid #000
text-align center
.dummy-height 
min-height 500px
.border
border 2px solid #000
.buttons
border 2px solid #000
padding 20px
.borderless 
border none
// App.css
.App
text-align center
.App-logo 
animation App-logo-spin infinite 20s linear
height 80px
.App-header 
height 150px
padding 20px
.App-intro 
font-size large
.login-transition
text-align center
padding-top 50vh
.debugger
text-align left
@keyframes App-logo-spin 
from { transform: rotate(0deg) }
to { transform: rotate(360deg) }

18. Lastly, update src/components/shared/Routes.jswith the following code snippet:

//src/components/shared/Routes.js
import React from 'react'
//Routes
import NotFound from './NotFound'
import App from './../App';
import LoginTransition from './../LoginTransition';
import { BrowserRouter, Route, Switch } from 'react-router-dom'
const Routes = (props) => {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact component={App} />
<Route path="/auth" exact component={LoginTransition} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
)
}
export default Routes

18. Just one more step to setup in Auth0. Log into your Auth0 account. Navigate to Clients on the left menu panel. Click on the client ID that you have used for the Rails API Server. Under settings, ensure Allowed Callback URLs include:

http://localhost:9000 
http://localhost:9000/auth
Ensure http://localhost:9000 and http://localhost:9000/auth is included under Allowed Callback URL

18. Now, we are ready to test our React front-end to see if it would interact with our Rails API server.

19. In your terminal, run npm run watch

20. In your favourite browser, navigate to localhost:9000 and click on the Sign In / Sign Up button. Sign in with the username admin@admin.com and password admin (Note: as mentioned earlier, it is assumed you have set a dummy username and password)

21. You can now play around with all the buttons as shown in the gif below !

React + Auth0 + Rails API demo

If you run into issues in any of the steps, it is a good exercise to try to debug it on your own. I have an publication that suggests a couple of ways to problem solve issues here which may help.

Hope this little write up would give you a starting point for your app and some learning at the same time !

And till next time … Keep hacking !