From 39f5c4a75341e29a957910cb9f044797f9540ea2 Mon Sep 17 00:00:00 2001
From: Alex Patterson <139158618+alex-fusionauth@users.noreply.github.com>
Date: Tue, 10 Oct 2023 18:23:59 -0400
Subject: [PATCH] Quickstart ruby on rails api (#2528)
* copy over
* update ruby on rails api
* remove direct link
* pr updates
* pr fixes
* use cookies
* add redirects
* remove old
* remove rails api from example apps
* updated based on pr suggestions
* correct ports
* stomped on redirect
* add quickstart to exampleapps
* update all with json
---
.../quickstart-ruby-on-rails-api.mdx | 404 ++++++++++++++++++
.../docs/quickstarts/quickstart-sections.ts | 7 -
site/_data/exampleapps.yaml | 2 +-
site/_layouts/doc.liquid | 2 +-
.../tutorials/integrate-ruby-rails-api.md | 199 ---------
5 files changed, 406 insertions(+), 208 deletions(-)
create mode 100644 astro/src/content/quickstarts/quickstart-ruby-on-rails-api.mdx
delete mode 100644 site/docs/v1/tech/tutorials/integrate-ruby-rails-api.md
diff --git a/astro/src/content/quickstarts/quickstart-ruby-on-rails-api.mdx b/astro/src/content/quickstarts/quickstart-ruby-on-rails-api.mdx
new file mode 100644
index 0000000000..59f87c0d29
--- /dev/null
+++ b/astro/src/content/quickstarts/quickstart-ruby-on-rails-api.mdx
@@ -0,0 +1,404 @@
+---
+title: Ruby on Rails
+description: Quickstart Integration of a Ruby on Rails API with FusionAuth
+navcategory: getting-started
+prerequisites: Ruby, bundler and Rails
+section: api
+technology: Ruby on Rails
+language: Ruby
+icon: /img/icons/ruby-on-rails.svg
+faIcon: fa-gem
+color: red
+cta: EmailListCTA
+coderoot: https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-api/main
+---
+import Aside from '../../components/Aside.astro';
+import Diagram from '../../diagrams/quickstarts/resource-server.astro';
+import RemoteCode from '../../components/RemoteCode.astro';
+
+In this article, you are going to learn how to integrate a Ruby on Rails API with FusionAuth. You will protect an API resource from unauthorized usage. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins.
+
+The docker compose file and source code for a complete application are available at https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-api.
+
+## Prerequisites
+
+- [Ruby 2.7.x](https://rubyonrails.org/): This quickstart was built using Ruby 2.7. This example may work on different versions of Rails, but it has not been tested.
+- [Rails 7.0.x.x](https://rubyonrails.org/): This quickstart was built using Rails 7.0.7.2. This example may work on different versions of Rails, but it has not been tested.
+- [SQLite](https://www.sqlite.org/download.html): This quickstart was built using sqlite3 (please note MacOS comes with sqlite3).
+- [Docker](https://www.docker.com): The quickest way to stand up FusionAuth. Ensure you also have [docker compose](https://docs.docker.com/compose/) installed.
+- (Alternatively, you can [Install FusionAuth Manually](/docs/v1/tech/installation-guide/)).
+
+## General Architecture
+
+A client wants access to an API resource at `/resource`. However, it is denied this resource until it acquires an access token from FusionAuth.
+
+
+
+While the access token is acquired via the Login API above, this is for simplicity of illustration. The token can be, and typically is, acquired through one of the [OAuth grants](/docs/v1/tech/oauth/).
+
+## Getting Started
+
+In this section, you’ll get FusionAuth up and running and create a resource server which will serve the API.
+
+### Clone The Code
+
+First off, grab the code from the repository and change into that directory.
+
+```
+git clone https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-api
+cd fusionauth-quickstart-ruby-on-rails-api
+```
+
+### Run FusionAuth Via Docker
+
+In the root directory of the repo you’ll find a Docker compose file (docker-compose.yml) and an environment variables configuration file (.env). Assuming you have Docker installed on your machine, you can stand up FusionAuth up on your machine with:
+
+```
+docker compose up -d
+```
+
+Here you are using a bootstrapping feature of FusionAuth, called [Kickstart](/docs/v1/tech/installation-guide/kickstart). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to a certain initial state.
+
+
+
+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 teller username is `teller@example.com` and your password is `password`. They will have the role of `teller`.
+* Your example customer username is `customer@example.com` and your password is `password`. They will have the role of `customer`.
+* Your admin username is `admin@example.com` and your password is `password`.
+* Your fusionAuthBaseUrl is 'http://localhost:9011/'
+
+You can log into the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want, but with Docker/Kickstart you don't need to.
+
+## Create Your Ruby on Rails Resource Server Application
+
+Now you are going to create a Ruby on Rails API application. While this section builds a simple API, you can use the same configuration to integrate an existing API with FusionAuth.
+
+We are going to be building an API backend for a banking application called ChangeBank. This API will have two endpoints:
+- `make-change`: This endpoint will allow you to call GET with a `total` amount and receive a response indicating how many nickels and pennies are needed to make change. Valid roles are `customer` and `teller`.
+- `panic`: Tellers may call this endpoint to call the police in case of an incident. The only valid role is `teller`.
+
+Both endpoints will be protected such that a valid JSON web token (JWT) will be required in the `Authorization` header in order to be accessed. Additionally, the JWT must have a `roles` claim containing the appropriate role to use the endpoint.
+
+
+
+### Initialize The Application
+
+Initialize the Ruby on Rails application using the following:
+
+```
+rails new your-application --api
+cd your-application
+```
+
+Once this is complete, you will see a new directory called `your-application` with several sub directories.
+
+You need to add the following lines to the `Gemfile` in the application root directory to include two new dependencies:
+
+```shell
+gem 'rack-jwt', git: 'https://github.com/FusionAuth/rack-jwt'
+gem 'dotenv-rails'
+```
+
+Your full `Gemfile` will now look like
+
+
+
+Then, install these new gems, by issuing the following command in your terminal window.
+
+```shell
+bundle install
+```
+### Update Routes Config
+
+Update `/config/routes.rb` to include the two new routes that will be created for `/make-change` and `/panic`. Your `routes.rb` file should now match the below code.
+
+
+
+### Add Security
+
+Create a new file to hold your environment variables directly in the `your-application` directory called `.env.development`. You will need to add two variables that are used to call your FusionAuth instance `FUSIONAUTH_LOCATION` and `CLIENT_ID`, with the values that match below.
+
+```
+# for rails
+
+FUSIONAUTH_LOCATION=http://localhost:9011
+
+CLIENT_ID=e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
+```
+
+Utilizing the enviroment variables you just added, you can now setup JSON Web Token (JWT) based authentication. Create a new file `config/initializers/jwt_rack.rb`. This initializer is used to hold configuration settings that are made after all of the frameworks and plugins are loaded.
+
+
+
+Having this code protects your endpoints from anonymous users and passes the JWT payload to the controller. The JWT payload includes `roles` encoded in the JWT you receive from FusionAuth. The decoded payload of a JWT for a `teller` might look like this:
+
+```json
+{
+ "aud": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
+ "exp": 1689289585,
+ "iat": 1689285985,
+ "iss": "http://localhost:9011",
+ "sub": "00000000-0000-0000-0000-111111111111",
+ "jti": "ebaa4184-2320-47dd-925b-2e18756c635f",
+ "authenticationType": "PASSWORD",
+ "email": "teller@example.com",
+ "email_verified": true,
+ "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
+ "roles": [
+ "teller"
+ ],
+ "auth_time": 1689285985,
+ "tid": "d7d09513-a3f5-401c-9685-34ab6c552453"
+}
+```
+
+### Add Controllers
+
+Create a new file for the MakeChangeController `app/controllers/make_change_controller.rb`. This controller will verify that the authenticated user has either the `teller` or `customer` role. It then takes in the url parameter `total` to calculate which coins will be returned in the JSON payload.
+
+
+
+Create a new file for the PanicController `app/controllers/panic_controller.rb`. This controller will verify that the authenticated user has only the `teller` role. It will respond with `Proper role not found for user.` if this role is not found. This route also verifies that this is a `POST` request and if this is not true it will respond with `Only POST method is supported.` If both of these tests are passed the controller will return a successful message of `We've called the police!`.
+
+
+
+## Run the API
+
+Start the API resource server by running:
+
+In a command shell window, navigate to the root directory of your application and run the following command to start the server.
+
+```shell
+bundle e rails s -p 4001
+```
+
+### Get a Token
+
+There are [several ways to acquire a token](/docs/v1/tech/oauth/) in FusionAuth, but for this example you will use the [Login API](/docs/v1/tech/apis/login) to keep things simple.
+
+
+
+First let's try the requests as the `teller@example.com` user. Based on the configuration this user has the `teller` role and should be able to use both `/make-change` and `/panic`.
+
+Acquire an access token for `teller@example` by making the following request
+
+```shell
+curl --location 'http://localhost:9011/api/login' \
+--header 'Authorization: this_really_should_be_a_long_random_alphanumeric_value_but_this_still_works' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "loginId": "teller@example.com",
+ "password": "password",
+ "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
+}'
+```
+Your response should look like the below JSON. Grab the token field (which begins with ey).
+
+```json
+{
+ "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InVOYl9iQzFySHZZTnZMc285VzRkOEprZkxLWSJ9.eyJhdWQiOiJlOWZkYjk4NS05MTczLTRlMDEtOWQ3My1hYzJkNjBkMWRjOGUiLCJleHAiOjE2ODkzNTMwNTksImlhdCI6MTY4OTM1Mjk5OSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDExIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMTExMTExMTExMTExIiwianRpIjoiY2MzNWNiYjUtYzQzYy00OTRjLThmZjMtOGE4YWI1NTI0M2FjIiwiYXV0aGVudGljYXRpb25UeXBlIjoiUEFTU1dPUkQiLCJlbWFpbCI6InRlbGxlckBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhcHBsaWNhdGlvbklkIjoiZTlmZGI5ODUtOTE3My00ZTAxLTlkNzMtYWMyZDYwZDFkYzhlIiwicm9sZXMiOlsiY3VzdG9tZXIiLCJ0ZWxsZXIiXSwiYXV0aF90aW1lIjoxNjg5MzUyOTk5LCJ0aWQiOiJkN2QwOTUxMy1hM2Y1LTQwMWMtOTY4NS0zNGFiNmM1NTI0NTMifQ.WLzI9hSsCDn3ZoHKA9gaifkd6ASjT03JUmROGFZaezz9xfVbO3quJXEpUpI3poLozYxVcj2hrxKpNT9b7Sp16CUahev5tM0-4_FaYlmUEoMZBKo2JRSH8hg-qVDvnpeu8nL6FXxJII0IK4FNVwrQVFmAz99ZCf7m5xruQSziXPrfDYSU-3OZJ3SRuvD8bMopSiyRvZLx6YjWfBsvGSmMXeh_8vHG5fVkq5w1IkaDdugHnivtJIivHuCfl38kQBgw9rAqJLJoKRHHW0Ha7vHIcS6OCWWMDIIVspLyQNcLC16pL9Nss_5v9HMofow1OvQ9sUSMrbbkipjKq2peSjG7qA",
+ "tokenExpirationInstant": 1689353059670,
+ "user": {
+ ...
+ }
+}
+```
+
+### Make the Request
+
+
+
+Make a request to `/make-change` with a query parameter `total=1.02`. Use the `token` as the `app.at` cookie.
+
+```shell
+curl 'http://localhost:4001/make-change?total=1.02' \
+--cookie 'app.at='
+```
+
+Alternatively you can make the same request by passing your token in the `Authorization` header.
+
+```shell
+curl --location 'http://localhost:4001/make-change?total=1.02' \
+--header 'Authorization: Bearer '
+```
+
+Your response should look like this:
+
+```json
+{
+ "Message": "We can make change using 20 quarters 1 dimes 0 nickels 2 pennies",
+ "Change": [
+ {
+ "Denomination": "quarters",
+ "Count": 20
+ },
+ {
+ "Denomination": "dimes",
+ "Count": 1
+ },
+ {
+ "Denomination": "nickels",
+ "Count": 0
+ },
+ {
+ "Denomination": "pennies",
+ "Count": 2
+ }
+ ]
+}
+```
+
+You were authorized, success! You can try making the request without the `--cookie` or with a different string rather than a valid token, and see that you are denied access.
+
+Next call the `/panic` endpoint because you are in trouble!
+
+```shell
+curl --location --request POST 'http://localhost:4001/panic' \
+--cookie 'app.at='
+```
+
+This is a `POST` not a `GET` because you want all your emergency calls to be non-idempotent.
+
+Your response should look like this:
+
+```json
+{"message":"We've called the police!"}
+```
+
+Nice, help is on the way!
+
+Now let's try as `customer@example.com` who has the role `customer`. Acquire a token for `customer@example.com`.
+
+```shell
+curl --location 'http://localhost:9011/api/login' \
+--header 'Authorization: this_really_should_be_a_long_random_alphanumeric_value_but_this_still_works' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "loginId": "customer@example.com",
+ "password": "password",
+ "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
+}'
+```
+
+Your response should look like the below JSON. Grab the token field (which begins with ey).
+
+```json
+{
+ "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InVOYl9iQzFySHZZTnZMc285VzRkOEprZkxLWSJ9.eyJhdWQiOiJlOWZkYjk4NS05MTczLTRlMDEtOWQ3My1hYzJkNjBkMWRjOGUiLCJleHAiOjE2ODkzNTQxMjMsImlhdCI6MTY4OTM1MzUyMywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDExIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMjIyMjIyMjIyMjIyIiwianRpIjoiYjc2YWMwMGMtMDdmNi00NzkzLTgzMjgtODM4M2M3MGU4MWUzIiwiYXV0aGVudGljYXRpb25UeXBlIjoiUEFTU1dPUkQiLCJlbWFpbCI6ImN1c3RvbWVyQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImFwcGxpY2F0aW9uSWQiOiJlOWZkYjk4NS05MTczLTRlMDEtOWQ3My1hYzJkNjBkMWRjOGUiLCJyb2xlcyI6WyJjdXN0b21lciJdLCJhdXRoX3RpbWUiOjE2ODkzNTM1MjMsInRpZCI6ImQ3ZDA5NTEzLWEzZjUtNDAxYy05Njg1LTM0YWI2YzU1MjQ1MyJ9.T1bELQ6a_ItOS0_YYpvqhIVknVMNeamcoC7BWnPjg2lgA9XpCmFA2mVnycoeuz-mSOHbp2cCoauP5opxehBR2lCn4Sz0If6PqgJgXKEpxh5pAxCPt91UyfjH8hGDqE3rDh7E4Hqn7mb-dFFwdfX7CMdKvC3dppMbXAGCZTl0LizApw5KIG9Wmt670339pSf5lzD38P9WAG5Wr7fAmVrIJPVu6yv2FoR-pMYD2lnAF63HWKknrWB-khmhr9ZKRLXKhP1UK-ThY1FSnmpp8eNblsBqCxf6WaYxYkdp5_F2e56M4sQwHzrg4P9tZGVCmMri4dShF3Ck7OGa7hel-iIPew",
+ "tokenExpirationInstant": 1689354123118,
+ "user": {
+ ...
+ }
+}
+```
+
+Now use that token to call `/make-change` with a query parameter `total=3.24`
+
+```shell
+curl --location 'http://localhost:4001/make-change?total=3.24' \
+--cookie 'app.at='
+```
+
+Your response should look like this:
+
+```json
+{
+ "Message": "We can make change using 12 quarters 2 dimes 0 nickels 4 pennies",
+ "Change": [
+ {
+ "Denomination": "quarters",
+ "Count": 12
+ },
+ {
+ "Denomination": "dimes",
+ "Count": 2
+ },
+ {
+ "Denomination": "nickels",
+ "Count": 0
+ },
+ {
+ "Denomination": "pennies",
+ "Count": 4
+ }
+ ]
+}
+```
+
+So far so good. Now let's try to call the `/panic` endpoint. (We're adding the `-i` flag to see the headers of the response)
+
+```shell
+curl -i --request POST 'http://localhost:4001/panic' \
+--cookie 'app.at='
+```
+
+You will get a `401 Unauthorized` response with the following message.
+
+```json
+{"message":"Proper role not found for user"}
+```
+
+Uh oh, I guess you are not allowed to do that.
+
+Enjoy your secured resource server!
+
+## Next Steps
+
+This quickstart is a great way to get a proof of concept up and running quickly, but to run your API in production, there are some things you're going to want to do.
+
+### FusionAuth Integration
+
+* Rather than call the Login API, you're probably going to want to use the [Authorization Code grant](/docs/v1/tech/oauth/#example-authorization-code-grant), which keeps all sensitive credentials within the bounds of FusionAuth. You can customize the [hosted login pages](/docs/v1/tech/themes/).
+* You may want to generate a token using the [Client Credentials grant](/docs/v1/tech/oauth/#example-client-credentials-grant) if you are calling the API from another service.
+
+### Security
+* Customize the [token expiration times and policies](/docs/v1/tech/oauth/#configure-application-oauth-settings) in FusionAuth
+* [Make sure you know how to securely consume a token](/articles/tokens/building-a-secure-jwt#consuming-a-jwt)
+* Secure your API [using an API gateway](/docs/v1/tech/developer-guide/api-gateways/) rather than at the framework layer.
+
+## Troubleshooting
+
+* I get `This site can’t be reached localhost refused to connect.` when I call the Login API.
+
+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).
+
+* The `/panic` endpoint doesn't work when I call it.
+
+Make sure you are making a `POST` call and using a token with the `teller` role.
+
+* It still doesn't work
+
+You can always pull down a complete running application and compare what's different.
+
+```
+git clone https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-api.git
+cd fusionauth-quickstart-ruby-on-rails-api
+docker-compose up -d
+cd complete-application
+bundle install
+bundle e rails s -p 4001
+```
diff --git a/astro/src/pages/docs/quickstarts/quickstart-sections.ts b/astro/src/pages/docs/quickstarts/quickstart-sections.ts
index e8205ea6ee..9ab13257f6 100644
--- a/astro/src/pages/docs/quickstarts/quickstart-sections.ts
+++ b/astro/src/pages/docs/quickstarts/quickstart-sections.ts
@@ -112,13 +112,6 @@ const qsSections: QuickStartSection[] = [
faIcon: 'fa-hashtag',
navColor: 'blue',
},
- {
- href: '/docs/v1/tech/tutorials/integrate-ruby-rails-api',
- title: 'Ruby on Rails',
- icon: '/img/icons/ruby-on-rails.svg',
- faIcon: 'fa-gem',
- navColor: 'red',
- },
{
href: '/docs/v1/tech/tutorials/integrate-express-api',
title: 'Express',
diff --git a/site/_data/exampleapps.yaml b/site/_data/exampleapps.yaml
index 823429ded0..8099757e27 100644
--- a/site/_data/exampleapps.yaml
+++ b/site/_data/exampleapps.yaml
@@ -39,7 +39,7 @@
name: Javascript JWT
description: JWT creation and decoding examples with javascript
language: javascript
-- url: https://github.com/fusionauth/fusionauth-example-rails-api-guide
+- url: https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-api
name: Rails apis
description: Protecting a Rails API with a JWT
language: ruby
diff --git a/site/_layouts/doc.liquid b/site/_layouts/doc.liquid
index cadf0cf710..839e350e45 100644
--- a/site/_layouts/doc.liquid
+++ b/site/_layouts/doc.liquid
@@ -90,7 +90,7 @@
diff --git a/site/docs/v1/tech/tutorials/integrate-ruby-rails-api.md b/site/docs/v1/tech/tutorials/integrate-ruby-rails-api.md
deleted file mode 100644
index 408df5a8e2..0000000000
--- a/site/docs/v1/tech/tutorials/integrate-ruby-rails-api.md
+++ /dev/null
@@ -1,199 +0,0 @@
----
-layout: doc
-title: Integrate Your Ruby on Rails API With FusionAuth
-description: Integrate your Ruby on Rails API with FusionAuth
-navcategory: getting-started
-prerequisites: Ruby, bundler and Rails
-technology: Ruby on Rails
-language: Ruby
----
-
-## Integrate Your {{page.technology}} API With FusionAuth
-
-{% include docs/integration/_intro-api.md %}
-
-## Prerequisites
-
-{% include docs/integration/_prerequisites.md %}
-
-## Download and Install FusionAuth
-
-{% include docs/integration/_install-fusionauth.md %}
-
-## Create a User and an API Key
-
-{% include docs/integration/_add-user.md %}
-
-## Configure FusionAuth
-
-Next, you need to set up FusionAuth. This can be done in different ways, but we’re going to use the [{{page.language}} client library](/docs/v1/tech/client-libraries/ruby). You can use the client library with an IDE of your preference as well.
-
-First, make a directory:
-
-```shell
-mkdir setup-fusionauth && cd setup-fusionauth
-```
-
-Then, create the required files:
-
-```shell
-touch Gemfile
-```
-
-Now, copy and paste the following file into `Gemfile`.
-
-```ruby
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-client-libraries/main/ruby/Gemfile %}
-```
-
-Install the gems.
-
-```shell
-bundle install
-```
-
-Create a file called `setup.rb`. Then copy and paste the following code into it.
-
-```ruby
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-client-libraries/main/ruby/setup-api.rb %}
-```
-
-Then, you can run the setup script.
-
-{% include _callout-note.liquid content="The setup script is designed to run on a newly installed FusionAuth instance with only one user and no tenants other than `Default`. To follow this guide on a FusionAuth instance that does not meet these criteria, you may need to modify the above script.
Refer to the [Ruby client library](/docs/v1/tech/client-libraries/ruby) documentation for more information." %}
-
-This will create the FusionAuth configuration for your {{page.technology}} API.
-
-```shell
-fusionauth_api_key=YOUR_API_KEY_FROM_ABOVE ruby setup.rb
-```
-
-If you are using PowerShell, you will need to set the environment variable in a separate command before executing the script.
-
-```shell
-$env:fusionauth_api_key='YOUR_API_KEY_FROM_ABOVE'
-ruby setup.rb
-```
-
-If you want, you can [log into your instance](http://localhost:9011) and examine the new API configuration the script created for you. You'd navigate to the Applications tab to do so.
-
-## Create Your {{page.technology}} API
-
-Now you are going to create a {{page.technology}} API. While this section builds a simple {{page.technology}} API, you can use the same configuration to build a more complex {{page.technology}} API.
-
-First, create the skeleton of the {{page.technology}} API. Rails has a nice generator to build this out.
-
-```shell
-rails new myapi --api && cd myapi
-```
-
-Now, update your `Gemfile` to look like this:
-
-```text
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-rails-api-guide/main/Gemfile %}
-```
-
-You may need to modify the version of ruby specified in the Gemfile. As long as Rails 7 is supported, you will be fine.
-
-Then, install these new gems.
-
-```shell
-bundle install
-```
-
-Next, create a file called `.env.development` and insert the following into it.
-
-```ini
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-rails-api-guide/main/.env.development %}
-```
-
-You can now start writing the code for your Rails API. First, let's create a controller which gives back a JSON message. Create a new file called `app/controllers/messages_controller.rb`, then add the following code:
-
-```ruby
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-rails-api-guide/main/app/controllers/messages_controller.rb %}
-```
-
-This controller returns a JSON array with messages.
-
-Next, update the `config/routes.rb` file to look like this:
-
-```ruby
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-rails-api-guide/main/config/routes.rb %}
-```
-
-This tells {{page.technology}} to return the content generated by the `messages_controller.rb` file when the `/messages` path is set up.
-
-You can now start up your server. You should do it in a new terminal window so that you can continue to edit the {{page.technology}} code.
-
-```shell
-bundle e rails s -p 4001
-```
-
-And visit [http://localhost:4001/messages](http://localhost:4001/messages) and view the JSON.
-
-Next, let's configure the token protection for this API. Go back to your previous terminal window and create a `config/initializers/jwt_rack.rb` file, and update it to look like this:
-
-```ruby
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-rails-api-guide/main/config/initializers/jwt_rack.rb %}
-```
-
-This tells {{page.technology}} to check for various attributes of the token, including the `iss` and the `aud`. Read the [`rack_jwt` gem documentation](https://github.com/FusionAuth/rack-jwt/) for more.
-
-You can also access the JWT in the controller. Below, the messages controller adds a special message if the user has a certain role.
-
-```ruby
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-example-rails-api-guide/main/app/controllers/messages_controller.rb %}
-```
-
-Now, back to the terminal where your server is running. Stop it (using `control-C`) and restart it.
-
-```shell
-bundle e rails s -p 4001
-```
-
-Visit [http://localhost:4001/messages](http://localhost:4001/messages), you'll get an error:
-
-```json
-{"error":"Missing token cookie and Authorization header"}
-```
-
-Your API is protected. Now, let's get an access token so authorized clients can get the API results.
-
-## Testing the API Flow
-
-There are a number of ways to get an access token, as mentioned, but for clarity, let's use the login API to mimic a client.
-
-Run this command in a terminal:
-
-```shell
-curl -H 'Authorization: YOUR_API_KEY_FROM_ABOVE' \
- -H 'Content-type: application/json' \
- -d '{"loginId": "YOUR_EMAIL", "password":"YOUR_PASSWORD","applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"}' \
- http://localhost:9011/api/login
-```
-
-Replace `YOUR_EMAIL` and `YOUR_PASSWORD` with the username and password you set up previously.
-
-This request will return something like this:
-
-```json
-{% remote_include https://raw.githubusercontent.com/FusionAuth/fusionauth-site/master/site/docs/src/json/users/login-response.json %}
-```
-
-Grab the `token` field (which begins with `ey`). Replace YOUR_TOKEN below with that value, and run this command:
-
-```shell
-curl --cookie 'app.at=YOUR_TOKEN' http://localhost:4001/messages
-```
-
-Here you are placing the token in a cookie named `app.at`. This is for compatibility with the FusionAuth best practices and [the hosted backend](/docs/v1/tech/apis/hosted-backend).
-
-If you want to store it in a different cookie or send it in the header, make sure you modify the `rack_jwt` initializer and restart the {{page.technology}} API.
-
-This will result in the JSON below.
-
-```json
-{"messages":["Hello"]}
-```
-
-