Authentication with JSON Web Tokens using Rails and React / Flux
<p>In a <a href="/2014/12/04/add-json-web-token-authentication-to-your-angular-rails-app">previous post</a>, I went over how to add authentication to your Rails + Angular app using JSON Web Tokens (JWT). This time, I’ll do the same, but using the <a href="2015/07/17/how-to-replace-the-angular-stack-with-the-react-ecosystem">React ecosystem</a>. But even if you’re using another front-end framework (Angular, Ember, Backbone), this post will be helpful because it fixes some issues with the previous server-side code that broke due to a change in the <a href="https://github.com/progrium/ruby-jwt">jwt gem</a>.</p> <p></p> <p>As I mentioned last time, I am a huge proponent of rolling your own authentication. It is not particularly complicated, plus I have found that the flexibility of a custom solution almost always comes in handy down the road and more than makes up for the up-front time-savings.</p> <p>Before getting started, you should go back and read the first 2 sections (Overview and Client/Server Data Flow) of the <a href="/2014/12/04/add-json-web-token-authentication-to-your-angular-rails-app">previous post</a> because I’m not going to repeat this information. Also, this tutorial assumes you already have a user model that includes a username and password.</p> <h2 id="server-side">Server Side</h2> <p>To get started, you’ll first need to install the <a href="https://github.com/progrium/ruby-jwt">jwt</a> gem.</p> <p>Next, let’s create a very simple abstraction for encoding and decoding auth tokens. So let’s create a file at <code>lib/auth_token.rb</code> and add the following code:</p> <div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">AuthToken</span> <span class="c1"># Encode a hash in a json web token</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">ttl_in_minutes</span> <span class="o">=</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">30</span><span class="p">)</span> <span class="n">payload</span><span class="p">[</span><span class="ss">:exp</span><span class="p">]</span> <span class="o">=</span> <span class="n">ttl_in_minutes</span><span class="p">.</span><span class="nf">minutes</span><span class="p">.</span><span class="nf">from_now</span><span class="p">.</span><span class="nf">to_i</span> <span class="no">JWT</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">secrets</span><span class="p">.</span><span class="nf">secret_key_base</span><span class="p">)</span> <span class="k">end</span> <span class="c1"># Decode a token and return the payload inside</span> <span class="c1"># If will throw an error if expired or invalid. See the docs for the JWT gem.</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">leeway</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span> <span class="n">decoded</span> <span class="o">=</span> <span class="no">JWT</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">secrets</span><span class="p">.</span><span class="nf">secret_key_base</span><span class="p">,</span> <span class="ss">leeway: </span><span class="n">leeway</span><span class="p">)</span> <span class="no">HashWithIndifferentAccess</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">decoded</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div> <p>The code is fairly straightforward. Note that the <code>decode</code> method could potentially raise on of several exceptions, so any calling code should account for this.</p> <p>Next, we need to implement the endpoint used by our login form. So create a new controller called <code>AuthController</code> with a single <code>authenticate</code> action. What it will do is verify the username and password and, if correct, create a new auth token that the client can then use to make authenticated requests. We implemented the <code>authentication_payload</code> method separately because we’re going to reuse it later.</p> <div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">AuthController</span> <span class="o"><</span> <span class="no">ApplicationController</span> <span class="k">def</span> <span class="nf">authenticate</span> <span class="c1"># You'll need to implement the below method. It should return the</span> <span class="c1"># user instance if the username and password are valid.</span> <span class="c1"># Otherwise return nil.</span> <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find_by_credentials</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:username</span><span class="p">],</span> <span class="n">params</span><span class="p">[</span><span class="ss">:password</span><span class="p">])</span> <span class="k">if</span> <span class="n">user</span> <span class="n">render</span> <span class="ss">json: </span><span class="n">authentication_payload</span><span class="p">(</span><span class="n">user</span><span class="p">)</span> <span class="k">else</span> <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span> <span class="ss">errors: </span><span class="p">[</span><span class="s1">'Invalid username or password'</span><span class="p">]</span> <span class="p">},</span> <span class="ss">status: :unauthorized</span> <span class="k">end</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">authentication_payload</span><span class="p">(</span><span class="n">user</span><span class="p">)</span> <span class="k">return</span> <span class="kp">nil</span> <span class="k">unless</span> <span class="n">user</span> <span class="o">&&</span> <span class="n">user</span><span class="p">.</span><span class="nf">id</span> <span class="p">{</span> <span class="ss">auth_token: </span><span class="no">AuthToken</span><span class="p">.</span><span class="nf">encode</span><span class="p">({</span> <span class="ss">user_id: </span><span class="nb">id</span> <span class="p">}),</span> <span class="ss">user: </span><span class="p">{</span> <span class="ss">id: </span><span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span> <span class="ss">username: </span><span class="n">user</span><span class="p">.</span><span class="nf">username</span> <span class="p">}</span> <span class="c1"># return whatever user info you need</span> <span class="p">}</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div> <p>And add a route to your <code>routes.rb</code> file to make it accessible:</p> <div class="highlight"><pre class="highlight ruby"><code><span class="n">post</span> <span class="s1">'authenticate'</span> <span class="o">=></span> <span class="s1">'auth#authenticate'</span> </code></pre></div> <p>Next, we need a way to authenticate each request and also find out who the current user is. We’ll add this code to our <code>ApplicationController</code>, exposing <code>authenticate_request!</code> and <code>current_user</code> methods to the rest of our controllers.</p> <div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">AccessDeniedError</span> <span class="o"><</span> <span class="no">StandardError</span> <span class="k">end</span> <span class="k">class</span> <span class="nc">NotAuthenticatedError</span> <span class="o"><</span> <span class="no">StandardError</span> <span class="k">end</span> <span class="k">class</span> <span class="nc">AuthenticationTimeoutError</span> <span class="o"><</span> <span class="no">StandardError</span> <span class="k">end</span> <span class="k">class</span> <span class="nc">ApplicationController</span> <span class="o"><</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">API</span> <span class="nb">attr_reader</span> <span class="ss">:current_user</span> <span class="c1"># When an error occurs, respond with the proper private method below</span> <span class="n">rescue_from</span> <span class="no">AuthenticationTimeoutError</span><span class="p">,</span> <span class="ss">with: :authentication_timeout</span> <span class="n">rescue_from</span> <span class="no">NotAuthenticatedError</span><span class="p">,</span> <span class="ss">with: :user_not_authenticated</span> <span class="kp">protected</span> <span class="c1"># This method gets the current user based on the user_id included</span> <span class="c1"># in the Authorization header (json web token).</span> <span class="c1">#</span> <span class="c1"># Call this from child controllers in a before_action or from</span> <span class="c1"># within the action method itself</span> <span class="k">def</span> <span class="nf">authenticate_request!</span> <span class="nb">fail</span> <span class="no">NotAuthenticatedError</span> <span class="k">unless</span> <span class="n">user_id_included_in_auth_token?</span> <span class="vi">@current_user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">decoded_auth_token</span><span class="p">[</span><span class="ss">:user_id</span><span class="p">])</span> <span class="k">rescue</span> <span class="no">JWT</span><span class="o">::</span><span class="no">ExpiredSignature</span> <span class="k">raise</span> <span class="no">AuthenticationTimeoutError</span> <span class="k">rescue</span> <span class="no">JWT</span><span class="o">::</span><span class="no">VerificationError</span><span class="p">,</span> <span class="no">JWT</span><span class="o">::</span><span class="no">DecodeError</span> <span class="k">raise</span> <span class="no">NotAuthenticatedError</span> <span class="k">end</span> <span class="kp">private</span> <span class="c1"># Authentication Related Helper Methods</span> <span class="c1"># ------------------------------------------------------------</span> <span class="k">def</span> <span class="nf">user_id_included_in_auth_token?</span> <span class="n">http_auth_token</span> <span class="o">&&</span> <span class="n">decoded_auth_token</span> <span class="o">&&</span> <span class="n">decoded_auth_token</span><span class="p">[</span><span class="ss">:user_id</span><span class="p">]</span> <span class="k">end</span> <span class="c1"># Decode the authorization header token and return the payload</span> <span class="k">def</span> <span class="nf">decoded_auth_token</span> <span class="vi">@decoded_auth_token</span> <span class="o">||=</span> <span class="no">AuthToken</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">http_auth_token</span><span class="p">)</span> <span class="k">end</span> <span class="c1"># Raw Authorization Header token (json web token format)</span> <span class="c1"># JWT's are stored in the Authorization header using this format:</span> <span class="c1"># Bearer somerandomstring.encoded-payload.anotherrandomstring</span> <span class="k">def</span> <span class="nf">http_auth_token</span> <span class="vi">@http_auth_token</span> <span class="o">||=</span> <span class="k">if</span> <span class="n">request</span><span class="p">.</span><span class="nf">headers</span><span class="p">[</span><span class="s1">'Authorization'</span><span class="p">].</span><span class="nf">present?</span> <span class="n">request</span><span class="p">.</span><span class="nf">headers</span><span class="p">[</span><span class="s1">'Authorization'</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="s1">' '</span><span class="p">).</span><span class="nf">last</span> <span class="k">end</span> <span class="k">end</span> <span class="c1"># Helper Methods for responding to errors</span> <span class="c1"># ------------------------------------------------------------</span> <span class="k">def</span> <span class="nf">authentication_timeout</span> <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span> <span class="ss">errors: </span><span class="p">[</span><span class="s1">'Authentication Timeout'</span><span class="p">]</span> <span class="p">},</span> <span class="ss">status: </span><span class="mi">419</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">forbidden_resource</span> <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span> <span class="ss">errors: </span><span class="p">[</span><span class="s1">'Not Authorized To Access Resource'</span><span class="p">]</span> <span class="p">},</span> <span class="ss">status: :forbidden</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">user_not_authenticated</span> <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span> <span class="ss">errors: </span><span class="p">[</span><span class="s1">'Not Authenticated'</span><span class="p">]</span> <span class="p">},</span> <span class="ss">status: :unauthorized</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div> <p>Now, for any protected method, add <code>authenticate_request!</code> to the beginning of the action method or in a before action filter.</p> <h2 id="client-side">Client Side</h2> <p>Now let’s switch to the client side. First, we’ll create a module that wraps our authentication api request. Next, we’ll need to implement a way to store our session info so that our React components can access it. And finally we’ll create our login form component.</p> <p>Note that this will only cover the bare minimum of what you’ll probably need. Among topics that won’t be covered are persisting the session to local storage (so that they can come back later and still be logged in) and renewing the session when it expires.</p> <p>Also, note that I’m using ES6 (or ES2015, as some like to call it) in all of my javascript code, transpiled using <a href="http://babeljs.io">babel</a>. I’m a long-time Coffeescript user, but I highly recommend moving towards ES6. It brings many of Coffeescript’s great features, plus the community as a whole is migrating towards it very quickly.</p> <h3 id="auth-api-wrapper">Auth API Wrapper</h3> <p>For my react apps, I use the <a href="https://github.com/mzabriskie/axios">axios</a> http library. But you can use any library you’d like. This is just a simple method that makes an HTTP Post request to the server-side endpoint we just created.</p> <p><strong>auth_api.js:</strong></p> <div class="highlight"><pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">axios</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">axios</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">loginPath</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/api/authenticate</span><span class="dl">'</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">AuthAPI</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">login</span><span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">{</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="nx">loginPath</span><span class="p">,</span> <span class="p">{</span><span class="na">username</span><span class="p">:</span> <span class="nx">username</span><span class="p">,</span> <span class="na">password</span><span class="p">:</span> <span class="nx">password</span><span class="p">})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">resp</span><span class="p">)</span> <span class="o">=></span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">data</span><span class="p">))</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">errResp</span><span class="p">)</span> <span class="o">=></span> <span class="nx">reject</span><span class="p">(</span><span class="nx">errResp</span><span class="p">.</span><span class="nx">data</span><span class="p">));</span> <span class="p">});</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">AuthAPI</span><span class="p">;</span> </code></pre></div> <h3 id="session-managment-using-flux">Session Managment using Flux</h3> <p>Before we implement our login form component, we need a way to maintain the state of our user session. The best way to do this is to implement the <a href="https://facebook.github.io/flux/">Flux</a> pattern. If you are new to the Flux pattern, you should read up on it first, but it is essentially a circular data flow that starts with your <strong>View</strong>, which triggers various <strong>Actions</strong>, which are responded to by your <strong>Stores</strong> who then update the application state, to which your <strong>View</strong> changes accordingly. And then this process repeats for each user action.</p> <p>For example, by clicking on our logout link component (the view), the logout action is triggered. Then the session store listens to this action and responds by clearing out the user and session info. Then the view, which is listening to changes on the session store, responds by hiding the logout link and showing the login link instead.</p> <p>There are a number of Flux implementations out there, but I’ll use <a href="https://github.com/spoike/refluxjs">Reflux</a> because it is simple and contains very little boilerplate code. So install it using NPM and then create two files: <code>auth_actions.js</code> and <code>session_store.js</code>. I think there’s some debate as to where data requests should originate within the flux architecture, but I’m going to go with Reflux’s suggestion of putting them in the Store.</p> <p><strong>auth_actions.js:</strong></p> <div class="highlight"><pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">Reflux</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">reflux</span><span class="dl">'</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">AuthActions</span> <span class="o">=</span> <span class="nx">Reflux</span><span class="p">.</span><span class="nx">createActions</span><span class="p">({</span> <span class="c1">// asyncResult creates 2 extra actions, one for success and one for failure</span> <span class="dl">'</span><span class="s1">loginRequest</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span> <span class="na">asyncResult</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">logout</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span> <span class="p">}</span> <span class="p">});</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">AuthActions</span><span class="p">;</span> </code></pre></div> <p><strong>session_store.js:</strong></p> <div class="highlight"><pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">Reflux</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">reflux</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">AuthActions</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth_actions</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">AuthAPI</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth_api</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// This object is where we'll store all the session state.</span> <span class="c1">// It will be a private variable and if any outside code</span> <span class="c1">// wants to access it, they'll need to use one of the</span> <span class="c1">// accessor methods below.</span> <span class="kd">let</span> <span class="nx">_sessionState</span> <span class="o">=</span> <span class="p">{</span> <span class="na">authRequestInProgress</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">authErrors</span><span class="p">:</span> <span class="p">[],</span> <span class="na">authToken</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="na">username</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> <span class="na">userId</span><span class="p">:</span> <span class="kc">null</span> <span class="p">};</span> <span class="kd">let</span> <span class="nx">SessionStore</span> <span class="o">=</span> <span class="nx">Reflux</span><span class="p">.</span><span class="nx">createStore</span><span class="p">({</span> <span class="c1">// Map all the actions in AuthActions to the corresponding</span> <span class="c1">// methods below</span> <span class="na">listenables</span><span class="p">:</span> <span class="p">[</span><span class="nx">AuthActions</span><span class="p">],</span> <span class="c1">// When a login request occurs, use the AuthAPI to make</span> <span class="c1">// an api request to the server and call the appropriate</span> <span class="c1">// action when it finishes.</span> <span class="c1">// Trigger a change to alert subscribers about the fact</span> <span class="c1">// that a request is in progress.</span> <span class="nx">onLoginRequest</span> <span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authRequestInProgress</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="nx">AuthAPI</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span> <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">AuthActions</span><span class="p">.</span><span class="nx">loginRequest</span><span class="p">.</span><span class="nx">completed</span><span class="p">)</span> <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">AuthActions</span><span class="p">.</span><span class="nx">loginRequest</span><span class="p">.</span><span class="nx">failed</span><span class="p">);</span> <span class="k">this</span><span class="p">.</span><span class="nx">trigger</span><span class="p">(</span><span class="nx">_sessionState</span><span class="p">);</span> <span class="p">},</span> <span class="c1">// When a login request completes successfully,</span> <span class="c1">// set the user info to the session state object and</span> <span class="c1">// trigger a change</span> <span class="c1">// an api request to the server and call the appropriate</span> <span class="c1">// action method when it finishes.</span> <span class="nx">onLoginRequestCompleted</span> <span class="p">(</span><span class="nx">resp</span><span class="p">)</span> <span class="p">{</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authRequestInProgress</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authErrors</span> <span class="o">=</span> <span class="p">[];</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authToken</span> <span class="o">=</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">auth_token</span><span class="p">;</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">username</span> <span class="o">=</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">trigger</span><span class="p">(</span><span class="nx">_sessionState</span><span class="p">);</span> <span class="c1">// You'll also need to redirect the user to the proper page,</span> <span class="c1">// but that's outside the scope of the article</span> <span class="p">},</span> <span class="c1">// When a login request fails, set the auth errors</span> <span class="c1">// and trigger a change</span> <span class="nx">onLoginRequestFailed</span> <span class="p">(</span><span class="nx">resp</span><span class="p">)</span> <span class="p">{</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authRequestInProgress</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authErrors</span> <span class="o">=</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">errors</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">trigger</span><span class="p">(</span><span class="nx">_sessionState</span><span class="p">);</span> <span class="p">},</span> <span class="c1">// When the user logs out, clear out the session state</span> <span class="c1">// and trigger a change</span> <span class="nx">onLogout</span> <span class="p">()</span> <span class="p">{</span> <span class="nx">_sessionInfo</span> <span class="o">=</span> <span class="p">{</span> <span class="na">authRequestInProgress</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">authErrors</span><span class="p">:</span> <span class="p">[],</span> <span class="p">};</span> <span class="k">this</span><span class="p">.</span><span class="nx">trigger</span><span class="p">(</span><span class="nx">_sessionState</span><span class="p">);</span> <span class="p">},</span> <span class="c1">// Accessor Methods</span> <span class="nx">getUsername</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span> <span class="p">}</span> <span class="nx">getUserId</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">_sessionState</span><span class="p">.</span><span class="nx">userId</span><span class="p">;</span> <span class="p">}</span> <span class="nx">isLoggedIn</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authToken</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">);</span> <span class="p">},</span> <span class="nx">getAuthErrors</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authErrors</span><span class="p">);</span> <span class="p">},</span> <span class="nx">isAuthRequestInProgress</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="nx">_sessionState</span><span class="p">.</span><span class="nx">authRequestInProgress</span> <span class="o">===</span> <span class="kc">true</span><span class="p">);</span> <span class="p">}</span> <span class="p">});</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">SessionStore</span><span class="p">;</span> </code></pre></div> <h3 id="login-form-component">Login Form Component</h3> <p>Now we need a simple login form component. Upon submission, it will trigger the <strong>loginRequest</strong> auth action, passing along the captured username and password.</p> <div class="highlight"><pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">AuthActions</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth_actions.js</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">SessionStore</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./session_store.js</span><span class="dl">'</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">LoginForm</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span> <span class="nx">handleLogin</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span> <span class="kd">let</span> <span class="nx">username</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">refs</span><span class="p">.</span><span class="nx">username</span><span class="p">.</span><span class="nx">getDOMNode</span><span class="p">().</span><span class="nx">value</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span> <span class="kd">let</span> <span class="nx">password</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">refs</span><span class="p">.</span><span class="nx">password</span><span class="p">.</span><span class="nx">getDOMNode</span><span class="p">().</span><span class="nx">value</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span> <span class="nx">AuthActions</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">password</span><span class="p">);</span> <span class="p">},</span> <span class="nx">renderAuthErrors</span><span class="p">()</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">errors</span> <span class="o">=</span> <span class="nx">SessionStore</span><span class="p">.</span><span class="nx">getAuthErrors</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="nx">errors</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="p">(</span> <span class="o"><</span><span class="nx">ul</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">AuthErrors</span><span class="dl">'</span><span class="o">></span><span class="p">{</span> <span class="nx">errors</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span> <span class="o"><</span><span class="nx">li</span><span class="o">></span><span class="p">{</span><span class="nx">err</span><span class="p">}</span><span class="o"><</span><span class="sr">/li> </span><span class="se">))</span><span class="sr"> }</u</span><span class="nx">l</span><span class="o">></span> <span class="p">);</span> <span class="p">},</span> <span class="nx">render</span><span class="p">()</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">buttonText</span> <span class="o">=</span> <span class="nx">SessionStore</span><span class="p">.</span><span class="nx">isAuthRequestInProgress</span><span class="p">()</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">Submitting...</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">Login</span><span class="dl">'</span><span class="p">;</span> <span class="k">return</span> <span class="p">(</span> <span class="o"><</span><span class="nx">form</span> <span class="nx">onSubmit</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleLogin</span><span class="p">}</span><span class="o">></span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">renderAuthErrors</span><span class="p">()</span> <span class="p">}</span> <span class="o"><</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="dl">'</span><span class="s1">text</span><span class="dl">'</span> <span class="nx">name</span><span class="o">=</span><span class="dl">'</span><span class="s1">username</span><span class="dl">'</span> <span class="nx">ref</span><span class="o">=</span><span class="dl">'</span><span class="s1">username</span><span class="dl">'</span> <span class="o">/></span> <span class="o"><</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="dl">'</span><span class="s1">password</span><span class="dl">'</span> <span class="nx">name</span><span class="o">=</span><span class="dl">'</span><span class="s1">password</span><span class="dl">'</span> <span class="nx">ref</span><span class="o">=</span><span class="dl">'</span><span class="s1">password</span><span class="dl">'</span> <span class="o">/></span> <span class="o"><</span><span class="nx">button</span> <span class="nx">disabled</span><span class="o">=</span><span class="p">{</span><span class="nx">SessionStore</span><span class="p">.</span><span class="nx">isAuthRequestInProgress</span><span class="p">()}</span><span class="o">></span><span class="p">{</span><span class="nx">buttonText</span><span class="p">}</span><span class="o"><</span><span class="sr">/button</span><span class="err">> </span> <span class="o"><</span><span class="sr">/form</span><span class="err">> </span> <span class="p">);</span> <span class="p">}</span> <span class="p">});</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">LoginForm</span><span class="p">;</span> </code></pre></div> <p>Now let’s create a “UserControls” component that, when logged in, will show the user’s username and a link to logout. Otherwise, it will show a link to the login page.</p> <div class="highlight"><pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">SessionStore</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./session_store.js</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">AuthActions</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth_actions.js</span><span class="dl">'</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">UserControls</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createClass</span><span class="p">({</span> <span class="nx">handleLogout</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span> <span class="nx">AuthActions</span><span class="p">.</span><span class="nx">logout</span><span class="p">();</span> <span class="p">},</span> <span class="nx">render</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">SessionStore</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">())</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="o"><</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">UserControls</span><span class="dl">'</span><span class="o">></span> <span class="o"><</span><span class="nx">span</span><span class="o">></span><span class="p">{</span><span class="nx">SessionStore</span><span class="p">.</span><span class="nx">getUsername</span><span class="p">}</span><span class="o"><</span><span class="sr">/span</span><span class="err">> </span> <span class="o"><</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="dl">'</span><span class="s1">#</span><span class="dl">'</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleLogout</span><span class="p">}</span><span class="o">></span><span class="nx">Logout</span><span class="o"><</span><span class="sr">/span</span><span class="err">> </span> <span class="o"><</span><span class="sr">/div</span><span class="err">> </span> <span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="o"><</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">UserControls</span><span class="dl">'</span><span class="o">></span> <span class="o"><</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="dl">'</span><span class="s1">#/login</span><span class="dl">'</span><span class="o">></span><span class="nx">Login</span><span class="o"><</span><span class="sr">/a</span><span class="err">> </span> <span class="o"><</span><span class="sr">/div</span><span class="err">> </span> <span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">});</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">UserControls</span><span class="p">;</span> </code></pre></div> <p>Finally, we need to configure Axios with a request “interceptor” so that it includes our auth token in the ‘Authorization’ header of every subsequent API request. This token is extracted and verified by our server so it knows the identity of the user. You’ll want to call this code as soon as your client app is initialized.</p> <div class="highlight"><pre class="highlight javascript"><code> <span class="k">import</span> <span class="nx">SessionStore</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./session_store.js</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">interceptors</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">config</span><span class="p">)</span> <span class="p">{</span> <span class="nx">config</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">Authorization</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Bearer </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">SessionStore</span><span class="p">.</span><span class="nx">getAuthToken</span><span class="p">();</span> <span class="k">return</span> <span class="nx">config</span><span class="p">;</span> <span class="p">});</span> <span class="p">}</span> </code></pre></div> <h2 id="conclusion">Conclusion</h2> <p>As I said above, there are still some pieces missing that are outside the scope of this article, but this should at least get you started towards a nice auth experience in your Rails / React application. This code was somewhat roughly extracted from an existing codebase, so if you find any problems, let me know <a href="https://twitter.com/adam_albrecht">on twitter</a>. Thanks!</p>