> For the complete documentation index, see [llms.txt](https://fusionauth.io/docs/llms.txt)

# Ruby on Rails | FusionAuth Docs

Quickstart integration of Ruby on Rails web application with FusionAuth.

# Ruby on Rails

[Edit on GitHub](https://github.com/FusionAuth/fusionauth-site/blob/main/astro/src/content/docs/get-started/quickstarts/web/quickstart-ruby-rails-web.mdx)

[View Markdown](https://fusionauth.io/docs/get-started/quickstarts/web/quickstart-ruby-rails-web.md)

In this quickstart, you are going to build an application with Ruby on Rails and integrate it with FusionAuth. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins. It'll have areas reserved for users who have logged in as well as public facing sections.

The Docker Compose file and source code for a complete application are available at [https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web](https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web).

## Prerequisites[#](#prerequisites)

*   [Ruby](https://www.ruby-lang.org/en/documentation/installation/): This will be needed for pulling down the various dependencies.
*   [Rails](https://guides.rubyonrails.org/getting_started.html): This will be used in order to run the Rails server.
*   [Docker](https://www.docker.com): The quickest way to stand up FusionAuth. (There are [other ways](https://fusionauth.io/docs/get-started/download-and-install.md)).

This app has been tested with Ruby 3.2.2 and Rails 7.0.4.3. This example should work with other compatible versions of Ruby and Rails.

## General Architecture[#](#general-architecture)

Here's a typical application login flow before FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application

    User ->> App : View Homepage
    User ->> App : Click Login Link
    App ->> User : Show Login Form
    User ->> App : Fill Out and Submit Login Form
    App ->> App : Authenticates User
    App ->> User : Display User's Account or Other Info
```

Request flow during login before FusionAuth

And here's the same application login flow after introducing FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application
    participant FusionAuth

    User ->> App : View Homepage
    User ->> App : Click Login Link (to FusionAuth)
    User ->> FusionAuth : View Login Form
    FusionAuth ->> User : Show Login Form
    User ->> FusionAuth : Fill Out and Submit Login Form
    FusionAuth ->> FusionAuth : Authenticates User
    FusionAuth ->> User: Go to Redirect URI
    User ->> App: Request the Redirect URI
    App ->> FusionAuth : Is User Authenticated?
    FusionAuth ->> App : User is Authenticated
    App ->> User : Display User's Account or Other Info
```

Request flow during login after FusionAuth

In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.

## Getting Started[#](#getting-started)

In this section, you'll get FusionAuth up and running and use Rails to create a new application.

### Clone the Code[#](#clone-the-code)

First off, grab the code from the repository and change into that directory.

```shell-session
$ git clone https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web.git
```

```shell-session
$ cd fusionauth-quickstart-ruby-on-rails-web
```

### Run FusionAuth via Docker[#](#run-fusionauth-via-docker)

You'll find a Docker Compose file (`docker-compose.yml`) and an environment variables configuration file (`.env`) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

```shell-session
docker compose up -d
```

Here you are using a bootstrapping feature of FusionAuth called [Kickstart](https://fusionauth.io/docs/get-started/download-and-install/development/kickstart.md). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing `docker compose down -v`, then re-run `docker compose up -d`.

FusionAuth will be initially configured with these settings:

*   Your client Id is `e9fdb985-9173-4e01-9d73-ac2d60d1dc8e`.
*   Your client secret is `super-secret-secret-that-should-be-regenerated-for-production`.
*   Your example username is `richard@example.com` and the password is `password`.
*   Your admin username is `admin@example.com` and the password is `password`.
*   The base URL of FusionAuth is `http://localhost:9011/`.

You can log in to the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the [FusionAuth app](http://localhost:9011/admin). The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the `View` icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the `OAuth configuration` section.

The `.env` file contains passwords. In a real application, always add this file to your `.gitignore` file and never commit secrets to version control.

### Create your Ruby on Rails Application[#](#create-your-ruby-on-rails-application)

Now you are going to create a Ruby on Rails application. While this section builds a simple Ruby on Rails application, you can use the same configuration to integrate your existing Ruby on Rails application with FusionAuth.

```shell-session
$ rails new myapp && cd myapp
```

## Authentication[#](#authentication)

We'll use the [OmniAuth Library](https://github.com/omniauth/omniauth), which simplifies integrating with FusionAuth and creating a secure web application.

### Configure OmniAuth[#](#configure-omniauth)

Install the omniauth gem and other supporting gems. Add the following three lines to your `Gemfile`.

```ruby
gem "omniauth"
gem "omniauth-rails_csrf_protection"
gem "omniauth_openid_connect"
```

Then, install them.

```shell-session
$ bundle install
```

Next, update your `config/environments/development.rb` file with FusionAuth OpenID Connect (OIDC) environment specific configuration.

You'll have to add similar configuration to the correct environment files when deploying to prod or other environments.

```ruby
# fusionauth oidc configuration.  In production, change issuer to FusionAuth production url
config.x.fusionauth.issuer = "http://localhost:9011"
config.x.fusionauth.client_id = "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
```

Configure OmniAuth by creating `config/initializers/omniauth.rb` with the following code:

```ruby
# only if you want a link instead of a button for login
#OmniAuth.config.allowed_request_methods = [:post, :get]

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :openid_connect,
  name: :fusionauth,
  scope: [:openid, :email, :profile],
  response_type: :code,
  issuer: Rails.configuration.x.fusionauth.issuer,
  ssl: false,
  client_options: {
    # discovery doesn't work with local development
    authorization_endpoint: Rails.configuration.x.fusionauth.issuer+"/oauth2/authorize",
    token_endpoint: Rails.configuration.x.fusionauth.issuer+"/oauth2/token",
    userinfo_endpoint: Rails.configuration.x.fusionauth.issuer+"/oauth2/userinfo",
    jwks_uri: Rails.configuration.x.fusionauth.issuer+"/.well-known/jwks.json",
    identifier: Rails.configuration.x.fusionauth.client_id,
    secret: ENV["OP_SECRET_KEY"],
    redirect_uri: 'http://localhost:3000/auth/fusionauth/callback',
    send_nonce: false
  }
end
```

This pulls values from the environment file and configures the omniauth gem to communicate with FusionAuth.

### Add Controllers[#](#add-controllers)

Next, you can create some controllers with the following shell commands:

```shell-session
$ rails generate controller auth
$ rails generate controller home
$ rails generate controller make_change
```

These controllers have the following purposes:

*   `auth` is for omniauth integration
*   `home` is an unprotected home page with a login button
*   `make_change` is a protected page for our example bank application

First, let's update the `config/routes.rb` file. Here's what that should look like:

```ruby
Rails.application.routes.draw do
  get 'make_change', to: "make_change#index"
  get 'logout', to: 'auth#logout'
  get 'auth/:provider/callback', to: 'auth#callback'
  root to: 'home#index'
end
```

Some simple routes corresponding to the controllers:

*   `make_change` is the protected bank page
*   `home` is the home page, which is available to unauthenticated users. This is also the default page.
*   `logout` is tied to the auth controller's logout method.
*   `auth/:provider/callback` is the omniauth callback method, which completes the OIDC grant.

Now, update the auth controller at `app/controllers/auth_controller.rb` to look like this, which completes some of the routes defined above.

```ruby
class AuthController < ApplicationController

  skip_before_action :authenticate_user!

  def logout
    session[:user] = nil
    redirect_to Rails.configuration.x.fusionauth.issuer+"/oauth2/logout?client_id="+Rails.configuration.x.fusionauth.client_id
  end

  def callback
    session[:user] = request.env['omniauth.auth'].info
    redirect_to '/'
  end
end
```

This lets you have a nice `logout` method and also handle the callback from omniauth. The latter sets a **session** attribute with user data, which can be used by views later.

Now, update the application controller at `app/controllers/application_controller.rb`.

```ruby
class ApplicationController < ActionController::Base

   before_action :authenticate_user!
   before_action :redirect_non_localhost!

   def authenticate_user!
     redirect_to '/login' unless session[:user]
   end

   def redirect_non_localhost!
    # Ensure we're on the same hostname/url registered as the callback URL in FA
    redirect_to('http://localhost:3000', allow_other_host: true) unless request.host == "localhost"
   end
end
```

*   `authenticate_user!` enforces authentication for all routes in your application by checking for the session attribute set by the auth controller after a successful login.
*   `redirect_non_localhost!` ensures users access the web app via `localhost` instead of a URL like http:127.0.0.1:3000/ . FusionAuth's origin and redirect URL configurations in this example expect `localhost`. In production, update this with your domain name.

Now, let's build out the home page. Update the home controller at `app/controllers/home_controller.rb` to look like this:

```ruby
class HomeController < ApplicationController

  skip_before_action :authenticate_user!

  def index
  end
end
```

You're skipping authentication for this route. After all, a user has to have someplace to go if they are unauthenticated, right?

Finally, we'll add some business logic for logged in users to make change with the following code in `app/controllers/make_change_controller.rb`:

```ruby
class MakeChangeController < ApplicationController
  def index
    if defined? params[:amount]
      amount = params[:amount].to_d
      @formatted_amount = sprintf("%0.2f", amount)
      @nickels = (amount / 0.05).to_i
      @pennies = ((amount - 0.05*@nickels) / 0.01).round
    end
  end
end
```

## App Customization[#](#app-customization)

In this section, you'll turn your application into a trivial banking application with some styling.

### Add Views[#](#add-views)

The view is welcoming, but prompts them to login. Otherwise, it shows a mock account balance. Replace `app/views/home/index.html.erb` with this code:

```erb
<% if !session[:user] %>
  <h1>Login to manage your account</h1>
<% else %>
  <h1>Welcome <%= session[:user]["first_name"] %>!</h1>
  <br/>
  Your account balance is $100.00
<% end %>
```

Next, update the layout so the user has login or logout buttons on every page. Add the below code to `app/views/layouts/application.html.erb` just after the `<body>` tag.

```erb
  <div id="page-container">
    <div id="page-header">
      <div id="logo-header">
        <%= image_tag "example_bank_logo.svg", class:"headerImage"%>

        <% if !session[:user] %>
          <%= form_tag('/auth/fusionauth', method: 'post', data: {turbo: false}) do %>
            <button type='submit' class='button-lg'>Login</button>
          <% end %>
        <% else %>
          <%= session[:user]["email"] %>
          &nbsp;
          <%= button_to "Logout", '/logout', method:'get', class:'button-lg' %>
        <% end %>
      </div>

      <div id="menu-bar" class="menu-bar">
        <% if session[:user] %>
          <a href="/" class="menu-link">Account</a>
          <a href="/make_change" class="menu-link">Make Change</a>
        <% else %>
          <a href="/" class="menu-link">Home</a>
        <% end %>
      </div>
    </div>

    <div style="flex: 1;">
      <div class="column-container">
        <div class="content-container">

          <%= yield %>

        </div>
        <% if !session[:user] %>
          <div style="flex: 0;">
            <%= image_tag "money.jpg", style:"max-width: 800px;"%>
          </div>
        <% end %>
      </div>
    </div>
</div>
```

Finally, add a form and messaging for making change at `app/views/make_change/index.html.erb`:

```erb
<h1>We Make Change</h1>
  <% if params.has_key?(:amount) %>
  <p>We can make change for
    $<%= @formatted_amount %>
  with
  <%= @nickels %> nickels and
  <%= @pennies %> pennies!
    <% end %>
</p>
<%= form_with url: "/make_change", method: :get do |form| %>
  <%= form.label :amount, "Amount in USD: $" %>
  <%= form.text_field :amount, placeholder:"0.00", :autofocus=>true %>
  <%= form.submit "Make Change" %>
<% end %>
```

### Add Styling[#](#add-styling)

Now, add some image assets and styling to make this look like a real application with the following shell commands:

```shell-session
$ curl -o app/assets/images/example_bank_logo.svg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/images/example_bank_logo.svg
$ curl -o app/assets/images/fusion_auth_logo.svg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/images/fusion_auth_logo.svg
$ curl -o app/assets/images/money.jpg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/images/money.jpg
$ curl -o app/assets/stylesheets/changebank.css https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/stylesheets/changebank.css
```

Once you've created these files, you can test the application out.

## Run the App[#](#run-the-app)

Start up the Rails application using this command:

```shell-session
OP_SECRET_KEY="super-secret-secret-that-should-be-regenerated-for-production" bundle exec rails s
```

`OP_SECRET_KEY` is the client secret, which was defined by the [FusionAuth Installation via Docker](#run-fusionauth-via-docker) step. You don't want to commit secrets like this to version control, so use an environment variable.

You can now open up an incognito window and visit the Rails app at [http://localhost:3000](http://localhost:3000) . Log in with the user account you created when setting up FusionAuth, and you'll see the email of the user next to a logout button.

## Made it this far? Want a free t-shirt? We got ya.

Thank you for spending some time getting familiar with FusionAuth.

\*Offer only valid in the United States and Canada, while supplies last.

## Next Steps[#](#next-steps)

This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.

### FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:

*   [Hosted pages](https://fusionauth.io/docs/customize/look-and-feel.md) such as login, registration, email verification, and many more.
*   [Email templates](https://fusionauth.io/docs/customize/email-and-messages/email-templates.md).
*   [User data and custom claims in access token JWTs](https://fusionauth.io/articles/tokens/jwt-components-explained.md).

### Security

*   [OmniAuth OpenIDConnect](https://github.com/omniauth/omniauth) handles token validation and refresh.
*   You may want to customize the [token expiration times and policies](https://fusionauth.io/docs/lifecycle/authenticate-users/oauth.md) in FusionAuth.
*   Choose [password rules](https://fusionauth.io/docs/get-started/core-concepts/tenants.md#password) and a [hashing algorithm](https://fusionauth.io/docs/reference/password-hashes.md) that meet your security needs.

### Tenant and Application Management

*   Model your application topology using [Applications](https://fusionauth.io/docs/get-started/core-concepts/applications.md), [Roles](https://fusionauth.io/docs/get-started/core-concepts/roles.md), [Groups](https://fusionauth.io/docs/get-started/core-concepts/groups.md), [Entities](https://fusionauth.io/docs/get-started/core-concepts/groups.md), and more.
*   Set up [MFA](https://fusionauth.io/docs/lifecycle/authenticate-users/multi-factor-authentication.md), [Social login](https://fusionauth.io/docs/lifecycle/authenticate-users/identity-providers.md), or [SAML](https://fusionauth.io/docs/lifecycle/authenticate-users/identity-providers/overview-samlv2.md) integrations.
*   Integrate with external systems using [Webhooks](https://fusionauth.io/docs/extend/events-and-webhooks.md), [SCIM](https://fusionauth.io/docs/lifecycle/migrate-users/scim.md), and [Lambdas](https://fusionauth.io/docs/extend/code/lambdas.md).

## Troubleshooting[#](#troubleshooting)

*   I get `This site can't be reached localhost refused to connect.` when I click the Login button

Ensure FusionAuth is running in the Docker container. You should be able to login as the admin user, `admin@example.com` with a password of `password` at [http://localhost:9011/admin](http://localhost:9011/admin).

*   I get an error page when I click on the Login button with message of `"error_reason" : "invalid_client_id"`

Ensure the value for `config.x.fusionauth.client_id` in the file `config/environments/development.rb` matches client Id configured in FusionAuth for the Example App Application at [http://localhost:9011/admin/application/](http://localhost:9011/admin/application/).

*   I'm getting an error from Rails after logging in

```plaintext
Rack::OAuth2::Client::Error
invalid_client :: Invalid client authentication credentials.
```

This indicates that OmniAuth is unable to call FusionAuth to validate the returned token. It is likely caused because of an incorrect client secret. Ensure the **`OP_SECRET_KEY`** environment variable used to start rails matches the FusionAuth ExampleApp client secret. You can review that by logging in as the admin user and examining the Application at [http://localhost:9011/admin/application/](http://localhost:9011/admin/application/)

*   It still doesn't work

You can always pull down a complete running application and compare what's different.

```shell-session
git clone https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web.git
cd fusionauth-quickstart-ruby-on-rails-web
docker compose up -d
cd complete-app
bundle install
OP_SECRET_KEY=super-secret-secret-that-should-be-regenerated-for-production bundle exec rails s
```