{"id":11902,"date":"2025-01-29T10:36:44","date_gmt":"2025-01-29T10:36:44","guid":{"rendered":"https:\/\/www.bacancytechnology.com\/qanda\/?p=11902"},"modified":"2025-01-30T05:35:56","modified_gmt":"2025-01-30T05:35:56","slug":"user-authentication-using-rails-and-react","status":"publish","type":"post","link":"https:\/\/www.bacancytechnology.com\/qanda\/ruby-on-rails\/user-authentication-using-rails-and-react","title":{"rendered":"User Authentication Using Rails and React"},"content":{"rendered":"<ul>\n<li>It&#8217;s one way to handle user authentication in a Rails API app with a React frontend.<\/li>\n<li>To resolve this issue, we need to configure the rails application with session storage to use cookies and session.<\/li>\n<li>Configure Cross-Origin Resource Sharing<\/li>\n<li>Controller to check whether the user is logged in or not<\/li>\n<li>State management in front-end with React and Redux<\/li>\n<li>Send a request to the isLoggedIn API from the React App component (the entry point of the React application) to verify if the user is logged in. Then, store this information in the Redux store to make it accessible throughout the application.<\/li>\n<\/ul>\n<h2>Add middleware for cookies and session:<\/h2>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">config.session_store :cookie_store, key: '_interslice_session\u2019\r\nconfig.middleware.use ActionDispatch::Cookies\r\nconfig.middleware.use config.session_store, config.session_options\r\n<\/pre>\n<p><strong>Configure Cross-Origin Resource Sharing (CORS)<\/strong> in config\/initializers\/cors.rb<br \/>\nChange the origin URL to the URL of localhost (later the deployed app\u2019s domain address) and set the credentials to true.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">Rails.application.config.middleware.insert_before 0, Rack::Cors do\r\n allow do\r\n   origins 'http:\/\/localhost:3001'\r\nresource '*',\r\n     headers: :any,\r\n     methods: [:get, :post, :put, :patch, :delete, :options, :head],\r\n     credentials: true\r\n end\r\nend\r\n<\/pre>\n<h3>Configuration for Cookie Storage<\/h3>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">Rails.application.config.session_store :cookie_store, {\r\n :key =&gt; '_your_app_name',\r\n :domain =&gt; :all,\r\n :same_site =&gt; :none,\r\n :secure =&gt; :true,\r\n :tld_length =&gt; 2\r\n}\r\n<\/pre>\n<p>Create a method in the SessionsController to verify if a user exists in the session.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">def create\r\n     @user = User.new(user_params)\r\n     if @user.save\r\n       session[:user_id] = @user.id\r\n       render json: {\r\n         status: :created,\r\n         user: @user\r\n       }\r\n     else\r\n       @user.save\r\n       render json: {\r\n         status: 500,\r\n         error: @user.errors.full_messages\r\n       }\r\n     end\r\n   end\r\n     def is_logged_in?\r\n       @current_user = User.find(session[:user_id]) if session[:user_id]\r\n       if @current_user\r\n         render json: {\r\n           logged_in: true,\r\n           user: User.new(@current_user)\r\n         }\r\n       else\r\n         render json: {\r\n           logged_in: false\r\n         }\r\n       end\r\n     end\r\n<\/pre>\n<p><strong>With the following routes:<\/strong><br \/>\n<code>get \u2018\/logged_in\u2019, to: \u2018sessions#is_logged_in?\u2019<\/code><\/p>\n<h2>App Component in React:<\/h2>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">import React, { useEffect } from 'react';\r\nimport '.\/App.css';\r\nimport { useSelector, useDispatch } from 'react-redux';\r\nimport { BrowserRouter as Router, Route, Switch } from 'react-router-dom';\r\nimport { fetchLoginStatus } from '.\/actions\/fetchLoginStatus';\r\n\r\nimport NavBar from '.\/components\/NavBar';\r\nimport Home from '.\/components\/Home';\r\nimport Error from '.\/components\/Error';\r\nimport Signup from '.\/components\/users\/Signup';\r\n\r\nconst App = () =&gt; {\r\n const dispatch = useDispatch();\r\n const errors = useSelector((state) =&gt; state.errors);\r\n const user = useSelector((state) =&gt; state.user);\r\nuseEffect(() =&gt; {\r\n   dispatch(fetchLoginStatus());\r\n }, [dispatch]);\r\n\r\n const error = () =&gt; errors.length &gt; 0;\r\n\r\n return (<\/pre>\n<div>{error() &amp;&amp; } } \/&gt;<\/div>\n<p>); }; export default App;<\/p>\n<p><strong>App component that uses Redux for state management and React Router for navigation. Here&#8217;s a brief explanation:<\/strong><\/p>\n<p><b>Imports<\/b><span style=\"font-weight: 400;\">:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">React and hooks (useEffect, useSelector, useDispatch) for component functionality and state management.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">react-router-dom for routing within the app.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Custom components (NavBar, Home, Error, and Signup) and a Redux action (fetchLoginStatus).<\/span><\/li>\n<\/ul>\n<p><b>State Management:<\/b><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">useSelector accesses the Redux state to retrieve user.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">useDispatch dispatches actions to update the Redux state.<\/span><\/li>\n<\/ul>\n<p><b>Effect:<\/b><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">useEffect runs on component mount to dispatch the fetchLoginStatus action, checking if the user is logged in.<\/span><\/li>\n<\/ul>\n<p><b>Routing:<\/b><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Uses Router and Switch for navigation:<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">\/ renders the Home component.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">\/signup renders the Signup component, passing props explicitly<\/span><\/li>\n<\/ul>\n<h2>userReducer to store login user Data<\/h2>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">export default (\r\n state = {\r\n   isLoggedIn: false,\r\n   id: null,\r\n   username: '',\r\n },\r\n action\r\n) =&gt; {\r\n switch (action.type) {\r\n   case 'LOGIN_USER':\r\n     return {\r\n       isLoggedIn: true,\r\n       id: action.user.id,\r\n       username: action.user.username,\r\n     };\r\n   default:\r\n     return state;\r\n }\r\n};\r\n<\/pre>\n<h2>fetchLoginStatus Action:<\/h2>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"ruby\">import axios from 'axios';\r\nexport const fetchLoginStatus = () =&gt; (dispatch) =&gt; {\r\n axios\r\n   .get('http:\/\/localhost:3000\/api\/v1\/logged_in', { withCredentials: true })\r\n   .then((response) =&gt; {\r\n     if (response.data.logged_in) {\r\n       dispatch({\r\n         type: 'LOGIN_USER',\r\n         user: response.data.user,\r\n       });\r\n     }\r\n   })\r\n   .catch((error) =&gt;\r\n \/\/ displays Error message\r\n};\r\n\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s one way to handle user authentication in a Rails API app with a React frontend. To resolve this issue, we need to configure the rails application with session storage to use cookies and session. Configure Cross-Origin Resource Sharing Controller to check whether the user is logged in or not State management in front-end with [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":11903,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[11],"tags":[],"class_list":["post-11902","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ruby-on-rails"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts\/11902"}],"collection":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/comments?post=11902"}],"version-history":[{"count":2,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts\/11902\/revisions"}],"predecessor-version":[{"id":11911,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/posts\/11902\/revisions\/11911"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/media\/11903"}],"wp:attachment":[{"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/media?parent=11902"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/categories?post=11902"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bacancytechnology.com\/qanda\/wp-json\/wp\/v2\/tags?post=11902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}