Easy Authentication for Ruby On Rails Login

A couple of things have changed in the Rails world since we published our last blog post on Rails. For one, Rails 6.0 was released on August 16, 2019 (Yay progress!), and, for another, Omniauth had a CRSF vulnerability reported (not so great). Does this make you a bit anxious? Don’t worry! Today we will be going over how to create a Rails app from scratch with the new Rails release and go over how to protect ourselves from the CRSF vulnerability. We are also going to throw in some CRUD action, as well. Last but not least, we will use Okta to take care of authentication and user management in our Rails App.

To get rocking and rolling with this tutorial you will need:

Create a Rails Application for Login

Open up the terminal and create a brand new rails application:

rails new okta_crud
cd okta_crud/

Open your code editor and navigate to your Gemfile. It should look something like this:

Rails Gemfile

We are going to add some sweet gems to our gemfile, including:

Copy and paste this into your Gemfile:

gem 'omniauth-oktaoauth'
gem 'activerecord-session_store'
gem 'figaro'
gem  'devise'
gem 'omniauth-rails_csrf_protection', '~> 0.1'

It should look like this now:

Rails gem

Now, hit save in your editor.

Next, go to your terminal and do some Rails magic to install all your new gems, create a database for active record, install devise into your Rails app, and create a user model:

bundle install
rake db:create
rails g devise:install
rails g devise User

Nice! We have Devise setup. Now let’s do the session and omniauth pieces:

rails generate active_record:session_migration
rails g migration AddOmniauthToUsers provider:index uid:index

Let’s also create pages for home and account and a session controller:

rails g controller pages home account
rails g controller Sessions new create destroy

Now, let’s run the migrations in our db:

rake db:migrate

Next, let’s create the Omniauth Controller:

cd app/controllers
mkdir users
touch users/omniauth_callbacks_controller.rb

That’s enough terminal commands for now. Time to add some code!
Add the Omniauth callback in the file controllers/users/omniauth_callbacks_controller.rb:

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def oktaoauth
     @user = User.from_omniauth(request.env["omniauth.auth"])
      session[:oktastate] = request.env["omniauth.auth"]["uid"]
     redirect_to root_path
  end
end

Next, go to the session controller, in controllers/sessions_controller.rb, and replace with this code for a logout method:

class SessionsController < ApplicationController
  def new
  end

  def create
  end

  def destroy
    session[:oktastate] = nil
    @current_user = session[:oktastate]
    @session = session[:oktastate]
    redirect_to root_path
  end
end

Let’s edit our page controller. Go to controllers/pages_controller.rb and copy and paste this code:

class PagesController < ApplicationController
  before_action :user_is_logged_in?, except: :home

  def home
    puts(session[:oktastate])
    @current_user = User.find_by(uid: session[:oktastate])
  end

  def account
    @current_user = User.find_by(uid: session[:oktastate])
    puts(session[:oktastate])
  end
end

Go to the models/users.rb file and add a method:

devise :omniauthable, omniauth_providers: [:oktaoauth]
  def self.from_omniauth(auth)
    user = User.find_or_create_by(email: auth['info']['email']) do |user|
      user.provider = auth['provider']
      user.uid = auth['uid']
      user.email = auth['info']['email']
    end
  end

It should look something like this:

Rails user.rb

In routes.rb to replace your routes, we have to make some adjustments as below:

Rails.application.routes.draw do
  get 'sessions/new'
  get 'sessions/create'
  get 'sessions/destroy'
  root 'pages#home'
  get 'pages/account'
  devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Next, our Application Controller :

class ApplicationController < ActionController::Base
protect_from_forgery with: :exception

  def user_is_logged_in?
    if !session[:oktastate]
      print("user is not logged in")
      redirect_to user_oktaoauth_omniauth_authorize_path
    end
  end

  def after_sign_in_path_for(resource)
    request.env['omniauth.origin'] || root_path
  end

end

It should now look like this:

Rails application controller

Next, let’s edit some of our views. Go to pages views/pages/home.html.erb and add this code:

<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
<% if @current_user %>
<h1><%=@current_user.email %></h1>
<%= link_to "Edit your account", "/pages/account", class: "item" %>
<%= link_to "logout", ENV['OKTA_URL'] + "/login/signout?fromURI=http://localhost:3000/sessions/destroy", class: "item" %>
<% else %>
<%= link_to 'Sign in via Okta', user_oktaoauth_omniauth_authorize_path, method: :post %>
<% end %>

Nice!

Add CRUD and Auth to Your Ruby on Rails App

It’s time to add the page that allows us to do CRUD operations. In this case, we are going to add a form where a user can edit their profile information, specifically their favorite gemstone. In views/pages/account.html.erb, copy and paste this code:

<%= javascript_pack_tag 'account_js' %>
<% if @current_user %>
<h1><%=@current_user.email %></h1>
<%= link_to "home", :root, class: "item" %>
<%= link_to "Edit your account", "/pages/account", class: "item" %>
<%= link_to "logout", ENV['OKTA_URL'] + "/login/signout?fromURI=http://localhost:3000/sessions/destroy", class: "item" %>
<% else %>
<%= link_to 'Sign in via Okta', user_oktaoauth_omniauth_authorize_path, method: :post %>
<% end %>

<form id="accountForm">
     <h1>Your Favorite Gemstone </h1>

    <fieldset>
        <input type="text" id="gemname" name="gemname" />
    </fieldset>
    <input type="submit" value="submit"></input>
</form>

It looks like there’s a form, but where do we get data from? More importantly, where do we submit data from our form back into Okta’s backend? The answer is a js file!

Go to javascript/packs and create a new file called account_js.js. Make sure it is named properly because we reference it in our account.html.erb. In that file copy and paste this code:

document.addEventListener("DOMContentLoaded", async function () {
    var response = await fetch(`${process.env.OKTA_URL}/api/v1/users/me`, {
      credentials: "include",
      method: "get",
      headers: { "Content-Type": "application/json" }
    });
  
    var userdata = await response.json();
    console.log(userdata.profile);
    document.getElementById("gemname").value = userdata.profile.gemstone
    var wordInput = document.getElementById("gemname");
    var form_el = document.getElementById("accountForm");
    form_el.addEventListener("submit", function (evt) {
      evt.preventDefault();
      submitData();
    });
  
    function submitData() {
      console.log("do something with " + wordInput.value);
      var body = {
        profile: {
          gemstone: wordInput.value,
        },
      };
      fetch(`${process.env.OKTA_URL}/api/v1/users/me`, {
        credentials: "include",
        method: "post",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      }).then((response) => {
        console.log(response);
        if(response.status == 200) {
          window.location.reload()
        }
      });
    }
  });

Last but not least, we need to add initializers in config/initializers/devise.rb:

require 'omniauth-oktaoauth'
  config.omniauth(:oktaoauth,
                ENV['OKTA_CLIENT_ID'],
                ENV['OKTA_CLIENT_SECRET'],
                :scope => 'openid profile email',
                :fields => ['profile', 'email'],
                :client_options => {site: ENV['OKTA_ISSUER'], authorize_url: ENV['OKTA_ISSUER'] + "/v1/authorize", token_url: ENV['OKTA_ISSUER'] + "/v1/token"},
                :redirect_uri => ENV["OKTA_REDIRECT_URI"],
                :auth_server_id => ENV['OKTA_AUTH_SERVER_ID'],
                :issuer => ENV['OKTA_ISSUER'],
                :strategy_class => OmniAuth::Strategies::Oktaoauth)

Okay it is time to generate our application YAML:

bundle exec figaro install

In the recently added config/application.yml, add this configuration of environment variables. We are going to come back to it later.

OKTA_CLIENT_ID: "okta client id"

OKTA_CLIENT_SECRET: "okta client secret"

OKTA_ORG: "your org"
OKTA_DOMAIN: "okta"

OKTA_URL: "your okta url"

OKTA_ISSUER: "your okta oauth server issuer"

OKTA_AUTH_SERVER_ID: "default"

OKTA_REDIRECT_URI: "http://localhost:3000/users/auth/oktaoauth/callback"

Setup Okta as your Rails App External Auth Provider

Next, let’s go into Okta to get our information.

Dealing with user authentication in web apps is a huge pain for every developer. This is where Okta shines: it helps you secure your web applications with minimal effort.

Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register to sign up for a new account. If you already have an account, run okta login. Then, run okta apps create. Select the default app name, or change it as you see fit. Choose Web and press Enter.

Select Other. Then, change the Redirect URI to http://localhost:3000/users/auth/oktaoauth/callback and use http://localhost:3000 for the Logout Redirect URI.

What does the Okta CLI do?

The Okta CLI will create an OIDC Web App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. You will see output like the following when it’s finished:

Okta application configuration has been written to: /path/to/app/.okta.env

Run cat .okta.env (or type .okta.env on Windows) to see the issuer and credentials for your app.

export OKTA_OAUTH2_ISSUER="https://dev-133337.okta.com/oauth2/default"
export OKTA_OAUTH2_CLIENT_ID="0oab8eb55Kb9jdMIr5d6"
export OKTA_OAUTH2_CLIENT_SECRET="NEVER-SHOW-SECRETS"

Your Okta domain is the first part of your issuer, before /oauth2/default.

NOTE: You can also use the Okta Admin Console to create your app. See Create a Web App for more information.

Finally, our application user’s profile actually needs a custom value. Since Okta is an extensible Identity provider let’s extend it to add the custom attribute of “gemstone”. Run okta login and open the resulting URL in your browser. Log in to the Okta Admin Console and go to Directory > Profile Editor.

Find the Default User Schema and click the Profile button.

Click Add Attribute and enter gemstone for the display name and variable name. Click Save.

Next, find your custom attribute and click the pencil button next to it.

We need to make sure our users can edit it, so select Read-Write for the User permission.

All set! This is looking good. It’s time to try out your application and have fun! Go to the console and run:

rails s

Final Result

Your result should look something like the images below.

Final localhost

Final Okta login

Final logged in user

Final logged in favorite gemstone

You can get the full source code of the project from GitHub.

Happy coding!

Learn More About Ruby on Rails and OAuth

For more Entity Framework Core and Okta articles check out these posts:

Make sure to follow us on Twitter, subscribe to our YouTube Channel and check out our Twitch channel so that you never miss any awesome content!

Okta Developer Blog Comment Policy

We welcome relevant and respectful comments. Off-topic comments may be removed.