Rails 7.1 authentication from scratch
add rails 7.1 authentication without using a gem like devise
Used 39 times
M
Mathias Karstädt
Usage
It is best used within a clean new rails 7.1 project.
Commit all of your own changes before using this!
The template has to be run before you created any user models as this will generate a user model for you.
It will:
Commit all of your own changes before using this!
The template has to be run before you created any user models as this will generate a user model for you.
It will:
- create a user model
- a current model
- an authentication concern
- a user_sessions controller
- a users_controller
To reverse all the changes run:
git restore . git clean -df # this will delete all non commited files!!! bin/rails db:reset # will reset everything in your database!!!
inspired by:
- https://github.com/stevepolitodesign/rails-authentication-from-scratch
- https://dev.to/kevinluo201/building-a-simple-authentication-in-rails-7-from-scratch-2dhb
- https://gorails.com/episodes/rails-7-1-authentication-from-scratch
Run this command in your Rails app directory in the terminal:
rails app:template LOCATION="https://railsbytes.com/script/V33s2E"
Template Source
Review the code before running this template on your machine.
# add needed gems
gem('bcrypt')
run('bundle install')
# generates user model
generate(:model, 'user username email password_digest password_confirmation')
rails_command('db:migrate')
# adds authentication to user model
inject_into_file 'app/models/user.rb', after: "class User < ApplicationRecord\n" do
<<ONE
has_secure_password :password, validations: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, presence: true, uniqueness: true
normalizes :email, with: ->(email) { email.strip.downcase }
ONE
end
# create signup page
generate(:controller, 'users index new create', '--skip-routes')
route('resources :users, only: [:index, :new, :create]')
route("root 'users#index'")
file 'app/controllers/users_controller.rb', <<~TWO
class UsersController < ApplicationController
before_action :authenticate_user!, only: [:index]
def index
@users = User.all
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
flash[:notice] = "User created successfully"
redirect_to users_path
else
flash[:alert] = "User not created"
render :new, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
end
TWO
file 'app/views/users/index.html.erb', <<~THREE
<h1>Users#index</h1>
<%= link_to 'New User', new_user_path %>
<table>
<thead>
<tr>
<th>id</th>
<th>username</th>
<th>email</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.username %></td>
<td><%= user.email %></td>
</tr>
<% end %>
</tbody>
</table>
<%= button_to "Logout", user_session_path(id: current_user.id), method: :delete %>
THREE
file 'app/views/users/new.html.erb', <<~FOUR
<h1>Users#new</h1>
<%= form_with model: @user do |f| %>
<% if @user.errors.any? %>
<div>
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= f.label :username %><br>
<%= f.text_field :username, required: true %>
</div>
<div>
<%= f.label :email %><br>
<%= f.text_field :email, required: true %>
</div>
<div>
<%= f.label :password %><br>
<%= f.password_field :password, required: true %>
</div>
<div>
<%= f.label :password_confirmation %><br>
<%= f.password_field :password_confirmation, required: true %>
</div>
<p>
<%= f.submit %>
</p>
<% end %>
FOUR
inject_into_file 'app/views/layouts/application.html.erb', after: " <body>\n" do
<<~FIVE
<% flash.each do |type, msg| %>
<div>
<%= msg %>
</div>
<% end %>
FIVE
end
# create current model
file 'app/models/current.rb', <<~CURRENT
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
CURRENT
# create authentication concern
file 'app/controllers/concerns/authentication.rb', <<~AUTHCONCERN
module Authentication
extend ActiveSupport::Concern
included do
before_action :current_user
helper_method :current_user
helper_method :user_signed_in?
end
def login(user)
reset_session
session[:current_user_id] = user.id
end
def logout
reset_session
end
def redirect_if_authenticated
redirect_to root_path, alert: "You are already logged in." if user_signed_in?
end
def authenticate_user!
redirect_to new_user_session_path, alert: "You need to login to access that page." unless user_signed_in?
end
private
def current_user
Current.user ||= session[:current_user_id] && User.find_by(id: session[:current_user_id])
end
def user_signed_in?
Current.user.present?
end
end
AUTHCONCERN
# create sign in page for users
generate(:controller, 'user_sessions new create', '--skip-routes')
route('resources :user_sessions, only: [:new, :create, :destroy]')
file 'app/controllers/user_sessions_controller.rb', <<~SIX
class UserSessionsController < ApplicationController
before_action :authenticate_user!, only: [:destroy]
def new
@user = User.new
end
def create
if @user = User.authenticate_by(email: params[:user][:email], password: params[:user][:password])
login @user
redirect_to root_path, notice: "Signed in."
else
flash[:alert] = "Login failed"
redirect_to new_user_session_path
end
end
def destroy
logout
redirect_to root_path, notice: "Signed out."
end
end
SIX
file 'app/views/user_sessions/new.html.erb', <<~SEVEN
<h1>Login page</h1>
<%= form_with model: @user, url: user_sessions_path do |f| %>
<div>
<%= f.label :email %><br>
<%= f.text_field :email %>
</div>
<div>
<%= f.label :password %><br>
<%= f.password_field :password %>
</div>
<p>
<%= f.submit 'Login' %>
</p>
<% end %>
SEVEN
inject_into_file('app/controllers/application_controller.rb',
after: "class ApplicationController < ActionController::Base\n") do
<<~EIGHT
include Authentication
EIGHT
end