Overview, Authentication Choice, and App Generation
Hi and welcome to this course where we are going to learn how we can create a Rails API application that uses authentication. The use case for this is if you want to build out your own authentication from scratch, especially if you're building out authentication for a front-end application. So if you want to add a registration and a sign-in component to React or to Angular or Vue.js, then this will walk through exactly how to do that.
Now we are going to be implementing HTTP-only cookie authentication. There are two popular types of authentication. One is to use HTTP-only cookies. The other is to use what are called JWT tokens. Now I prefer to use HTTP-only cookies mainly because it leads to a much more straightforward interface. An API with JWT tokens you generate these randomized encrypted tokens and then both sides of the application have to do quite a bit of work to one, make sure that the JWT tokens are still accurate and that they're still matching, that they haven't been hijacked, and the front-end application has to store it in local storage or something like that, and then the backend constantly has to check that. It feels like that's not really the best fit, especially if you're a Rails developer. With Rails you have the ability to utilize the session.
What the session does, if you're familiar with MVC applications, you can store anything you want in a session, such as if a user is logged in or not. What we can do with an API is actually take advantage of this same exact set of features, so we can use the session the same way we would with a traditional MVC app even though it's an API. We're going to walk through all of the steps needed in order to do that.
In this guide we're going to generate an application from scratch and add the dependencies needed for building our own authentication, and then we're going to configure that app to work. So let's get started.
I'm going to create a new application here. So I'll say rails new and I'm just going to call this authentication_app and it's going to use the database of Postgres. While that is generating we are going to be able to see all of the dependencies installing. This is just going to give us our base app.
Now you may notice I did not use the API-only flag. Rails does have the ability to create an API-only application. However, if you do that you actually won't be able to use the session without having to add more dependencies back in, which kind of kills the entire point of doing this. So I'm generating a regular application just like a standard MVC app, but we're not going to use any of the views. We're going to treat it like an API-only application.
Now that that's been generated, let me jump into the authentication_app and I'm gonna start up tmux here. These feature-driven courses, I spend less time talking about things like my environment and really walking through every stage of syntax because I assume if you're going through these that you're already a Rails developer, you're familiar with the syntax, and you're simply wanting to follow along and use this like a recipe or a reference point for you to build your own features.
So I'm going to first create the database, and then after that we're going to add our two gems in. While it's generating the database we can go and I'm going to go to RubyGems so that we're working with the right versions here, and I'm first going to pull in bcrypt. It looks like it's 3.1.12, so I'm going to copy that to the clipboard and now let's open up the Gemfile.
As you can see here on the right-hand side I am using Ruby 2.5.1, and you'll see in the Gemfile that we're using the Rails version 5.2. However, the concepts we're talking about in this guide are going to apply for quite a long time. None of the things we're talking about are going to be deprecated anytime soon. So even if you're watching this and you're building a Rails 6 application, this is going to apply to you.
I'm going to come down to the very bottom here and add bcrypt, and one other gem is rack-cors. So I'm going to say gem 'rack-cors'. Now with rack-cors we're not pulling it in with a specific version; instead we're simply just going to say require 'rack/cors'. Let's save this, close it, run bundle to pull down all of those dependencies, and now we can start building out our configuration.
If you open up the file system you're going to want to add a couple files to your list of initializers. You're going to want to add a cors file and we'll talk about what that does when we build it out, and then we also need a session_store. So neither of those are here right now, so let's just create those files. I'm going to say touch config/initializers/cors.rb and then we're going to create session_store.rb.
Now if I open up our initializers you can go to those files and start creating them. So the first one is cors. CORS gives you the ability to whitelist certain domains because we're going to be passing secure cookies back and forth between the front-end application and the back-end application. Then we need to be able to use a tool called credentials. What's required with Rails is that if you're going to use credentials and you're going to work with the session then you have to implement a tool like CORS and give a specific set of rules for how you are going to be able to communicate. These rules are going to be found in this initializer.
The very first thing is we're going to set up Rails.application.config.middleware.insert_before 0, Rack::Cors module. All that's doing is saying that we're inserting a level of middleware here and we're using the Rack::Cors module to do it. All of the rules that we place inside here are going to be intercepted by the Rails config. We're saying at the very top of the chain we want to establish these rules because if an application tries to communicate with our system that's not authorized to do so and they're coming from a domain that we have not whitelisted, we don't want to give them any access to the system. That's what we're setting up here.
We're going to create an allow block, so I'm going to say allow do and then give an origin. I'll say origin and pass in a string, the domain. The first one I'm going to do is for localhost, so I'll say http://localhost:3000. Now this is going to change depending on what kind of front-end application you're working with and what port you're using. So if you're using Vue then you're probably going to use localhost:8080, or if you're using some specific type of React application that runs on a different port by default you're going to use whatever port it runs on. So say localhost:3000, and that is all we need to do for the origin.
Now we have to list off the resources that are allowed, so I'm going to say resource '*' and then for the headers we're going to allow any, and then the list of methods we're going to allow. These are the HTTP methods that we are going to define right here, and we're going to say, okay front-end application, you are allowed to use GET, POST, PUT, PATCH, DELETE, and two more that are important to put on here: OPTIONS and HEAD. Then close off that array. This is an array of symbols and it's just defining the methods that we're allowing. Obviously customize this to however your app is going to be using this, but for me I wanted to give you the full set of options that you can give just so you have that.
Now we have one more item to give here and it's going to be credentials true. I have this zoomed in so it's easy to read the text, but usually I haven't zoomed out; it's all on one line. So credentials true is what is going to allow you to pass those headers back and forth and pass the cookie from your front-end app to the backend app. That is very important. If you do not put that there it will not work.
I'm going to put that localhost entry in, and now I'm just going to comment this one or I'm going to put this one here as kind of a placeholder. You need to not only have your localhost environment but you have to have the domain that you are going to be pushing this up to. I'm assuming your React application or your Angular app is going to be on a server somewhere and on a domain. You need to allow it here. For me I'm going to say this is going to be on Heroku, so I'll say that the origin is http://jdh-authentication-app-react.herokuapp.com. You can call it whatever you want, something like authentication-app-react. That's where you're going to be putting the domain that your production app is going to be on.
Now if you had an app and you wanted to have it on the www and have it without that you need to list all of those here. This is where your production goes, and that's all you need to do. So I'm going to get out of cors and let's go to the session store.
The session store is where we define what the cookie's going to be structured like. With session_store we're going to say Rails.application.config, and then we're going to configure the session_store. This is going to be :cookie_store. We're saying that for sessions we're going to use cookies. This takes a few arguments. The first one is going to be the key. Now the key is going to be the name of the session cookie. For this one I'm just going to call it something like _authentication_app. Start it with an underscore; this is kind of the main convention that you will see when you're naming these so that inside of the browser when you go to an application you'll see that this is stored in that client and it's named this.
That's the key. We also have to define the domain. For the domain this is going to be a string and it's going to be whatever your domain is. So let's say that this app would be hosted on jdh-authentication-app-api.herokuapp.com. Now obviously that is another domain I completely made up. For the system I built for DevGuides in order to be able to have authentication for it this one domain was apiguides.com. So it's whatever the actual source application, the API application, its domain is. I'm going to save that.
Let's just test this application out to make sure it's working. We're going to have to go into our routes and give a base route. So route to static#home. We just need to create a static controller here. Let me do that: app/controllers/static_controller.rb. Class StaticController inherits from ApplicationController and then we have just one route that will be home. I want to render some JSON, so render json: { status: "it's working" }.
Okay, let's test all of this out. I'm going to run the Rails server and we should be able to go to / and it looks like we have a little error here. Oh yeah, undefined method origins did you or origin dat you mean origin? So that is just picking up a little typo inside of the cors config initializers cors. There we go. Yeah, it should be origins on both of these. That's why we test it out and don't keep on writing more code.
There we go. Okay, that looks like it's working. Let's open this up. I'm going to go to localhost:3000 and we should have some JSON returned to us, and yes we do: { "status": "it's working" }.
So we are all good to go. We have set up our application and we've given all of the key elements we need in order to implement authentication. In the next guide we're going to do just that.