passkeys-ruby-on-rails-reactPasskeys Implementation

Passkeys with Ruby on Rails and React

In this tutorial, we show how to build a sample application with passkey authentication using Ruby on Rails in the backend together with a React frontend.

Blog-Post-Author

Nicolai

Created: October 17, 2023

Updated: May 15, 2024


1. Introduction

2. Ruby on Rails passkey project prerequisites

3. Repository structure for Ruby on Rails passkey project

4. Set up your Corbado account and project

5. Set up Ruby on Rails passkeys app

    5.1. Create React app

    5.2. Deliver React build files with Ruby

    5.3. Configure environment variables

6. Create React passkey login page

    6.1. Embed the passkey authentication UI component

    6.2. Add route for passkey login page

7. Add React profile page

    7.1. Create Ruby on Rails endpoint

    7.2. Verify session

    7.3. Get data from Corbado session

    7.4. Create React component for profile page

    7.5. Add route for profile page

8. Start using passkeys with our Ruby on Rails - React implementation

9. Conclusion

1. Introduction

In this blog post, well be walking through the process of building a sample application with passkey authentication using Ruby on Rails in the backend together with a React frontend. To make passkeys work, we use Corbado's passkey-first UI components that automatically connects to a passkeys backend.

If you want to see the finished code, please have a look at our sample application GitHub repository.

The result looks as follows:

Corbado webcomponent

2. Ruby on Rails + React passkey project prerequisites

This tutorial assumes basic familiarity with Ruby on Rails, React, Typescript and HTML. Furthermore, make sure you have Ruby, Rails as well as Node.js installed and accessible from your shell. Lets dive in!

3. Repository structure for Ruby on Rails + React passkey project

Our Ruby on Rails + React project contains many files, but these are the most important ones:

... ├── app | ... | ├── controllers | | └── pages_controller.rb # Controller for our pages | | ├── config | ... | ├── environments | | ├── development.rb # Development environment config | | └── production.rb # Production environment config | | | └── routes.rb # The Ruby on Rails routes are configured here | └── frontend ... ├── .env └── src ... ├── index.js # Root of our React.js app which also contains the React.js routes └── routes ├── login.js # Login page containing the Corbado webcomponent └── profile.js # Profile page showing information about the current user

4. Set up your Corbado account and project

Visit the Corbado developer panel to sign up and create your account (you'll see a passkey sign-up in action here!).

Corbado developer panel

In the project setup wizard, begin by selecting an appropriate name for your project. For the product selection, opt for "Corbado Complete". Subsequently, specify your technology stack and select "DEV along with "Corbado session management" options. Afterwards, you'll get more foundational setup guidance.

Next, choose "Web app" as an application type and React as your framework. In the application settings, define your application url and relying party id as follows:

App settings

  • Application URL: Provide the URL where you embedded the UI component, here: http://localhost:3000/login.
  • Relying Party ID: Provide the domain (no protocol, no port and no path) where passkeys should be bound to, here: localhost

5. Set up Ruby on Rails passkey app

To initialize our project, we create a new Ruby on Rails project with

rails new ruby-react

This will create a ruby-react folder containing our Ruby on Rails project. Now, we can move on to create our frontend which will be delivered by Ruby in step 5.2.

5.1 Create React app

We head into our project with

cd ruby-react

and initialize our React frontend:

npx create-react-app frontend

We will bundle everything (all pages, styles and code) into a single HTML file for Ruby to deliver.

In the "frontend" folder, create a file "webpack.config.js" with the following content:

const Dotenv = require("dotenv-webpack"); module.exports = { plugins: [new Dotenv()], };

Make sure the contents of the scripts and config folders match the content of our sample frontend's respective folder. Also adjust your package.json file to match the one in the repo here to make sure you have all necessary dependencies (You can copy it and just change the app name).

If you now execute

npm install && npm run build

and open the build/index.html file you can see that our entire React app is packaged into that single HTML file.

5.2 Deliver React build files with Ruby

We want to deliver the HTML file we just produced with Ruby. We create a file called pages_controller.rb under app/controllers and give it the following content:

require 'net/http' require 'json' require 'jwt' class PagesController < ActionController::Base def manifest render file: 'frontend/build/manifest.json' end def home render file: 'frontend/build/index.html' end end

The "home" method sends the index.html that Is generated by React to the user. React has its own internal router which is included in our HTML file, so we can just always deliver this generated HTML file and it will display the correct page dependent on the current URL.

Next, we create a route in /config/routes.rb for the methods inside pages_controller.rb.

Rails.application.routes.draw do # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") # root "articles#index" match "/manifest.json", to: "pages#manifest", via: :get match "/", to: "pages#home", via: :get match "*path", to: "pages#home", via: :get end

This basically always sends back the HTML file which was generated by React.

5.3 Configure environment variables

We will need the Corbado project ID in the next steps, so we'll add it to our environment variables. We create an environment variable for Ruby and then one for React. Concerning Ruby, we add our variable to the development.rb and production.rb files under /config/environments/:

require "active_support/core_ext/integer/time" Rails.application.configure do # ... config.corbado_project_id = "pro-xxx" en

Concerning React, we have installed dotenv in step 5.1 (When you adjusted the package.json file). Therefore we can just create a .env file in the React project root (/frontend) and paste our project id there:

REACT_APP_CORBADO_PROJECT_ID=pro-xxx

6. Create React passkey login page

6.1 Embed the passkey authentication UI component

First, we'll create our login page under /frontend/src/routes/login.js with the content below. It contains only the Corbado UI component which will handle authentication for us. On successful signup/ login, we'll redirect the user to the profile page.

import {CorbadoAuth} from "@corbado/react"; import {useNavigate} from "react-router"; function Login() { const navigate = useNavigate() return ( <div> <CorbadoAuth onLoggedIn={() => {navigate("/profile")}} /> </div> ); } export default Login;

6.2 Add route for passkey login page

To make it work we modify index.js in our /frontend folder to include the react router with a route for /login.

import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import reportWebVitals from "./reportWebVitals"; import { createBrowserRouter, createRoutesFromElements, Navigate, Route, RouterProvider, } from "react-router-dom"; import Login from "./routes/login"; import {CorbadoProvider} from "@corbado/react"; const router = createBrowserRouter( createRoutesFromElements( <Route path="/"> <Route index path="/" element={<Navigate to="/login"/>}></Route> <Route path="/login" element={<Login/>}></Route> </Route> ) ); window.addEventListener("DOMContentLoaded", function (e) { ReactDOM.createRoot(document.getElementById("root")).render( <CorbadoProvider projectId={process.env.REACT_APP_CORBADO_PROJECT_ID}> <RouterProvider router={router}/> </CorbadoProvider> ); reportWebVitals(); });

Notice how we wrapped our entire application with the CorbadoProvider component. We have to pass it our project id, which we retrieve from our environment. Furthermore, we'll set the setShortSessionCookie option to true so that a cookie is created on login.

7. Add React profile page

After successful authentication, the Corbado UI component redirects the user to the provided Redirect URL (http://localhost:3000/profile). This page displays information about the user which our frontend (React) will get from our backend (Ruby on Rails). We will now create an endpoint which will provide that info.

7.1 Create Ruby on Rails endpoint

We first add a route in config/routes.rb:

Rails.application.routes.draw do # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") # root "articles#index" match "/api/profile", to: "pages#api_profile", via: :get match "/manifest.json", to: "pages#manifest", via: :get match "/", to: "pages#home", via: :get match "*path", to: "pages#home", via: :get end

and then we create the corresponding method in app/controllers/pages_controller.rb:

require 'net/http' require 'json' require 'jwt' class PagesController < ActionController::Base def manifest render file: 'frontend/build/manifest.json' end def home render file: 'frontend/build/index.html' end def api_profile end end

7.2 Verify session

In our backend, we will get the info from the Corbado session, but before we can use information embedded in the session, we need to verify that the session is valid. We therefore take the cbo_short_session cookie (the session) and verify its signature using the public key from Corbado. We also verify that the issuer is correct (use your Corbado project ID here):

require 'net/http' require 'json' require 'jwt' class PagesController < ActionController::Base def manifest render file: 'frontend/build/manifest.json' end def home render file: 'frontend/build/index.html' end def api_profile @project_id = Rails.application.config.corbado_project_id @user_id = session[:user_id] @user_name = session[:user_name] @user_email = session[:user_email] issuer = "https://#{@project_id}.frontendapi.corbado.io" jwks_uri = "https://#{@project_id}.frontendapi.corbado.io/.well-known/jwks" begin # Fetch JSON from the jwks_uri uri = URI(jwks_uri) response = Net::HTTP.get(uri) json = JSON.parse(response) # Get the public key publicKey = JSON.parse(json["keys"].first.to_json) # Verify the JWT token verifier = JWT::Verify.new({}, iss: issuer, verify_iat: true, algorithms: ["RS256"], jwks: publicKey ) cboShortSession = cookies[:cbo_short_session] decoded_token = JWT.decode(cboShortSession, nil, false, verifier: verifier).first # Check if the token is valid unless decoded_token return render json: { :error => "JWT token is not valid!" }.to_json, status: 400 end return render json: {}.to_json rescue => e puts e.message return render json: { :error => e.message }.to_json, status: 400 end end end

7.3 Get data from Corbado session

Finally, we can extract the information stored in the JWT claims. These are then sent to our frontend.

# Extract information from the token @user_id = decoded_token["sub"] @user_name = decoded_token["name"] @user_email = decoded_token["email"] return render json: { :user_id => @user_id, :user_name => @user_name, :user_email => @user_email }.to_json

7.4 Create React component for profile page

In the /frontend/routes folder, add a profile.js file with the following content:

import axios from "axios"; import {useCorbado} from "@corbado/react"; import {useState} from "react"; import {useNavigate} from "react-router-dom"; function Profile() { const [userID, setUserID] = useState("loading..."); const [userName, setUserName] = useState("loading..."); const [userEmail, setUserEmail] = useState("loading..."); const [checked, setChecked] = useState(false); const navigate = useNavigate() const { logout } = useCorbado() async function handleLogout() { console.log("Logout") try { await logout() } finally { navigate("/login") } } if (!checked) { axios .get(window.location.origin + "/api/profile") .then((response) => { console.log(response.data); setUserID(response.data.user_id); setUserName(response.data.user_name); setUserEmail(response.data.user_email); }) .catch((error) => { console.log(error); setUserID("Unauthorized"); setUserName("Unauthorized"); setUserEmail("Unauthorized"); }); setChecked(true); } return ( <div> <h2>:/protected 🔒</h2> <p>User ID: {userID}</p> <p>Name: {userName}</p> <p>Email: {userEmail}</p> <button id="logoutButton" onClick={handleLogout}> Logout </button> </div> ); }; export default Profile;

7.5 Add route for profile page

Next, also add a /profile route inside index.js:

import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import reportWebVitals from "./reportWebVitals"; import { createBrowserRouter, createRoutesFromElements, Navigate, Route, RouterProvider, } from "react-router-dom"; import Login from "./routes/login"; import Profile from "./routes/profile"; import {CorbadoProvider} from "@corbado/react"; const router = createBrowserRouter( createRoutesFromElements( <Route path="/"> <Route index path="/" element={<Navigate to="/login" />}></Route> <Route path="/profile" element={<Profile />}></Route> <Route path="/login" element={<Login />}></Route> </Route> ) ); window.addEventListener("DOMContentLoaded", function (e) { ReactDOM.createRoot(document.getElementById("root")).render( <CorbadoProvider projectId={process.env.REACT_APP_CORBADO_PROJECT_ID} setShortSessionCookie={true}> <RouterProvider router={router} /> </CorbadoProvider> ); reportWebVitals(); });

8. Start using passkeys with our Ruby on Rails implementation

To start our application, we first rebuild our React app by executing

npm run build

inside the "/frontend" directory. Afterwards we go back to the project root and start our Ruby on Rails app with

./bin/rails s

When visiting http://localhost:3000 you should see the following screen:

Corbado UI compoenent

After successful sign up / login, you see the profile page:

Passkey profile page

9. Conclusion

This tutorial showed how easy it is to add passwordless authentication with passkeys to a Ruby on Rails app with a React frontend using Corbado. Besides the passkey-first authentication, Corbado provides simple session management, that we used for a retrieval of basic user data. If you want to read more about how Corbado's session management please check the docs here.

Share this article


LinkedInTwitterFacebook

Table of Contents

Enjoyed this read?

🤝 Join our Passkeys Community

Share passkeys implementation tips and get support to free the world from passwords.

🚀 Subscribe to Substack

Get the latest news, strategies, and insights about passkeys sent straight to your inbox.


We provide UI components, SDKs and guides to help you add passkeys to your app in <1 hour

Start for free