루비 온 레일즈에서 devise gem 없이 로그인 기능 구현하기 (How to build authentication system without devise gem in Ruby on Rails)



##### 해당 포스트의 목표는 로그인 기능을 devise gem을 이용하지 않고 직접 구현하는 것입니다. 직접 구현해보면서 로그인 기능이 어떻게 구현되는지에 대해 이해해보고자 합니다.

##### 만약 Ruby on Rails 입문하고 싶거나, 입문하지 얼마 되지 않았다면, 해당 글을 따라해보기를 권합니다. 로그인에 대한 이해 뿐만 아니라, Ruby on Rails의 CRUD (Create, Read, Update), Background Job 등을 경험해볼 수 있기 때문입니다.

구현 순서는 아래와 같습니다. 루비 온 레일즈는 이미 다 설치되어 있다고 가정하고 진행합니다. *루비 온 레일즈 설치 방법*은 https://gorails.com/setup/osx/10.15-catalina 와 같은 사이트를 참고하시기 바랍니다.

#### 1. 회원가입 구현(feat. MVC(Model, View, Controller))
#### 2.쿠키를 이용한 로그인
#### 3. 세션을 이용한 로그인
#### 4. JWT를 이용한 로그인
#### 5. 사용자 메일 인증

---

### 1. 회원가입 구현(feat. MVC(Model, View, Controller))

로그인을 구현하기 위해 사용자 정보를 저장할 User table을 만들 것입니다.

**명령어로 한 번에 모델, DB 테이블, 테스트 파일 생성**

---

```bash
rails g model User
```

위와 같은 명령어로 model 파일과, migration 파일, test를 위한 파일들을 동시에 만들 수 있습니다만 직접 파일을 만드는 경우에는 아래와 같습니다.

**모델, DB 테이블, 테스트 파일을 따로 생성**

---

아래와 같이 models 폴더에 `user.rb` 파일을 만듭니다.

사진 1

만든 파일 안에 아래 코드를 입력합니다. 모델 파일을 만들었습니다.

user.rb

```ruby
class User < ApplicationRecord
end
```

터미널에서 아래와 같이 명령어를 입력합니다.

```
rails g migration create_users
```

그럼 아래와 같이 migrate 폴더에 migration 파일이 생성됩니다.

사진 2

만든 파일 안에 아래와 같이 코드를 입력합니다. **명령어로 한 번에 모델, DB 테이블, 테스트 파일 생성** 하는 경우에도 아래와 같이 코드를 작성해야 합니다.

아래 코드를 설명하자면 `t.string :email` `t.string :password_digest` 는 email, password 값을 저장할 column을 만듭니다. `unique: true` 는 user table에 같은 이메일을 저장할 수 없게 합니다. 쉽게 말해 이메일 중복 체크입니다.

 `password_digest` 라고 column 명을 지은 이유는 Rails에서 제공해주는 action 때문입니다. password_digest 라고 이름을 지으면 password_confirmaiton 을 함께 제공합니다. 회원가입 시 비밀번호, 비밀번호 확인 값을 함께 받아 두 개의 값이 동일해야지만 회원가입이 되는 기능인 것입니다.

 `t.timestamps` 는 created_at, updated_at 이라는 column을 table에 추가해주고, table에 row가 추가되거나, 수정될 때 created_at, updated_at 값을 생성합니다.

20xxxxxxxxxxxx_create_users.rb

```ruby
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :email
      t.string :password_digest

      t.timestamps
    end
    add_index :users, :email, unique: true
  end
end
```

---

migration 파일을 생성, 작성이 끝났음으로 rake db:migrate를 합니다. 

```bash
rake db:migrate
```

그러면 migration 파일이 실행되면서 테이블이 만들어지고 db 파일에 schema.rb 파일이 생성됩니다. `schema.rb` 파일에서 table 정보를 확인할 수 있습니다.

`app/models/user.rb` 파일에 가서 아래와 같이 코드를 입력해줍니다. 회원가입 시 (user table에 저장 시)email을 검증할 것인데, 존재해야하며(presence: true), 고유해야합니다.(uniqueness: true) d

```ruby
class User < ApplicationRecord
    validates :email, presence: true, uniqueness: true
end
```

이제 아주 간단한 회원가입, 로그인 시스템을 위한 model 작업이 끝났습니다. 

---

**Rails console을 사용해 테스트 해보기**

이제 Controller와 View 를 만들면 되는데요 그 전에 Rails console 을 사용해볼겸 방금 작성한 코드가 원하는대로 돌아가는지 Rails console 로 확인해보겠습니다. 터미널 창에 아래와 같이 입력합니다.

```bash
rails c
```

이제 console창에서 회원가입을 해보겠습니다. 아래와 같이 입력합니다.

```ruby
User.create(email: "example@example.com", password_digest: "example")
```

입력하면 아래와 같이 결과 값이 나올 것입니다. 회원가입이 된 것입니다. 

```bash
=> #<User id: 1, email: "example@example.com", password_digest: [FILTERED], created_at: "2020-03-27 03:26:42", updated_at: "2020-03-27 03:26:42">
```

한 번 똑같은 이메일로 회원가입을 해봅시다. `rollback transaction`이라는 문구가 출력될 것입니다. 회원가입이 되지 않은 것입니다. 정말 그런지 아래와 같이 입력해 User table 의 저장값을 확인해봅시다.

```ruby
User.all
```

아래와 같이 1명만 가입(저장)되었음을 확인할 수 있습니다.

```bash
#<ActiveRecord::Relation [#<User id: 1, email: "example@example.com", password_digest: [FILTERED], created_at: "2020-03-27 03:26:42", updated_at: "2020-03-27 03:26:42">]>
```

아래 명령어로 Rails console 을 종료하고 터미널로 복귀합니다.

```ruby
exit
```

---

이제 Controller 와 View 를 만들어 웹브라우저에서 회원가입, 로그인해보겠습니다.

아래와 같이 명령어를 입력해 Controller와 View(with Controller action) 를 동시에 생성할 수도  있지만, 직접 만들 수 있습니다.

**명령어로 생성하는 방법**

```bash
rails g controller home index
```

**직접 생성하는 방법**

아래 사진과 같이 controllers 폴더에 `home_controller.rb` 파일을 만듭니다.

사진3

만든  파일에 코드를 입력합니다.

home_controller.rb

```ruby
class HomeController < ApplicationController
    def index
    end
end
```

그 다음 `app/views` 폴더에 가서 home 이라는 폴더를 만들고 그 폴더 안에 index.html.erb 라는 파일을 만들어줍니다.

만든 파일에 아래와 같이 작성합니다. 이 부분은 큰 의미가 없습니다. 

```html
<h2> Hello World ! </h2>
```

그 다음 route 설정을 하러 `config/routes.rb 로 `갑니다. url 주소와 controller action(index)를 매칭시키기 위함입니다. 아래와 같이 설정 후, 웹브라우저에서localhost:3000/home/index 을 입력하면 views/home/index.html.erb 파일이 웹브라우저에 출력됩니다.

```ruby
Rails.application.routes.draw do
	get 'home/index'
end
```

아래와 같이 입력하면 localhost:3000 주소를 입력해도 localhost:3000/home/index를 입력한 것과 같이 views/home/index.html.erb 파일이 나타납니다.

```ruby
Rails.application.routes.draw do
	get 'home/index'
  root 'home#index'
end
```

이제 터미널에 `rails s` 를 입력한 후 웹브라우저에서 `localhost:3000`으로 이동합니다.(url 주소에 입력합니다.)

그러면 index.html.erb 파일에 입력했던 Hello World ! 가 보일 것입니다. 이제 여기에 로그인하러 가는 버튼과 회원가입하러 가는 버튼을 만들어줍니다.

views/home/index.html.erb 파일에 아래와 같이 입력합니다. 일부러 2가지 방식으로 입력했습니다. 표현은 다르지만 둘은 동일합니다. 로그인은 html 방식, 회원가입은 Rails 에서 사용하는 방식으로 표현한 것입니다.

```html
<h2> Hello World ! </h2>

<a href="#"> 로그인 </a>
<hr/>
<%= link_to "회원가입", "#" %>
```

이제 로그인 버튼을 누르면 로그인 페이지로 넘어가고, 회원가입 버튼을 누르면 회원가입 페이지로 넘어가도록 Controller 와 View 를 만들겠습니다. 먼저 회원가입 페이지부터 만들겠습니다. 아래와 같이 명령어로 만들어보겠습니다.

```ruby
rails g controller user new create
```

생각해보니 user가 아니라 users 로 하는 것이 Rails 방식에 더 적합합니다. 이미 만든 user controller 는 다음과 같이 삭제할 수 있습니다. model 도 동일합니다.

```bash
rails d controller user
```

다시 users controller 를 생성합니다.

```bash
rails g controller users new create
```

routes.rb 에 가보면 `get 'users/new'` `get 'users/create'` `get 'user/new'` `get 'user/create' `가 입력되어있을 것입니다.

다음과 같이 수정합니다. 

```ruby
Rails.application.routes.draw do
  get 'users/new'
  post 'users/create'
  get 'home/index'
  
  root 'home#index'
end
```

이제 다시 `views/home/index.html.erb` 파일에 가서 아래와 같이 입력합니다.

```html
<h2> Hello World ! </h2>

<a href="#"> 로그인 </a>
<hr/>
<%= link_to "회원가입", users_new_path %>
```

웹브라우저를 새로고침하고 회원가입 버튼을 누르면 Users#new Find me in app/views/users/new.html.erb 라는 글을 확인할 수 있습니다. `app/views/users/new.html.erb ` 파일에 있는 글이 출력되는 것입니다.

`users_new_path` 가 무엇인지 궁금하신 분은 터미널에 `rake routes` 를 입력해보면 routes.rb 파일에 설정한 주소들의 정보를 볼 수 있습니다. 정보 중 users_new GET /users/new(.:format) users#new 라는 정보를 확인할 수 있습니다. /users/new 라는 url을 users_new_path 로 대체할 수 있는 이유입니다.

이제 `app/views/users/new.html.erb `로 가서 회원가입 폼을 입력해줍니다.

```html
<h2>회원가입</h2>

<%= form_with url: "/users/create" do |f|%>
    <%= f.label :email %>
    <%= f.text_field :email %>

    <%= f.label :password_digest %>
    <%= f.password_field :password_digest %>

    <%= f.submit %>
<% end %>
```

위의 내용은 html로 변환하면 아래와 같습니다.

```html
<form action="/users/create" accept-charset="UTF-8" data-remote="true" method="post">
  <input type="hidden" name="authenticity_token" value="NRkFyRWxdYNfUg7vYxLOp2SLf93lvnl+QwDWorR42Dp6yZXPhHEb6arhDOIWcqGit8jfnrPwL781/xlrzj63TA==" />
  <input type="text" name="user[email]" id="user_email" />
  <input type="password" name="user[password_digest]" id="user_password_digest" />
  <input type="submit" name="commit" value="Save" data-disable-with="Save" />
</form>
```

이제 users_controller.rb 에 가서 create 액션에 아래와 같이 적어줍니다.

```ruby
class UsersController < ApplicationController
  def new
  end

  def create
    @user = User.new
    @user.email = params[:email]
    @user.password_digest = params[:password_digest]
    
    if @user.save
      redirect_to root_path
    else
      redirect_to users_new_path
    end
  end
end
```

만약 회원가입이 제대로 되면, /home/index 로 올 것이고, 회원가입이 되지 않으면 다시 회원가입 페이지가 나올 것입니다.

하지만 회원가입에는 늘 비밀번호를 적는 칸과 비밀번호 확인을 위하 비밀번호 확인 칸이 있습니다. 이를 구현해보겠습니다. 구현하기 위해 `routes.rb` 파일에 가서 아래와 같이 수정합니다.

```ruby
Rails.application.routes.draw do
  resources :users
  get 'home/index'
  root 'home#index'
end
```

저장 후 `rake routes` 로 routes 정보를 보면 users 와 관련한 주소들이 생긴 것을 볼 수 있습니다. User 테이블과 관련한 CRUD(Create, Read, Update, Delete)에 필요한 주소들이 Restful 하게 설정되어있습니다.

`app/views/home/index.html.erb` 에 가서 바뀐 path를 수정합니다.

```html
<h2> Hello World ! </h2>

<a href="#"> 로그인 </a>
<hr/>
<%= link_to "회원가입", new_user_path %>
```

그리고 app/`views/users/new.html.erb` 에 가서 아래와 같이 수정합니다.

```html
<h2>회원가입</h2>

<%= form_with model: @user do |f|%>
    <%= f.label :email %>
    <%= f.text_field :email %>

    <%= f.label :password %>
    <%= f.password_field :password %>

    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation %>

    <%= f.submit %>
<% end %>
```

다음으로 `app/views/controllers/users_controller.rb` 에 가서 아래와 같이 수정합니다. 참고로 private 아래 있는 액션들은 url 주소로 호출할 수 없으며, before_action 은 특정 액션이 일어나기 전에 먼저 실행되는 함수가 필요한 경우 사용합니다. 아래의 경우에는 create 액션이 일어나기 전에 user_params 액션이 일어납니다.

```ruby
class UsersController < ApplicationController
  before_action :user_params, only: [:create]

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    
    if @user.save
      redirect_to root_path
    else
      redirect_to users_new_path
    end
  end

  private
  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end
```

그 다음 gem 을 이용할 것입니다 `gemfile` 에 들어가서 `gem 'bcrypt', '~> 3.1.7'`을 추가합니다. 이미 주석 처리가 되어 있을텐데 그 주석을 푸셔도 좋습니다.

```
# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'
```

그리고 터미널에 `bundle install` 을 입력합니다. 입력 후 레일즈 서버를 껐다가 다시 킵니다.(`ctrl+c` =>`rails s`)

그 다음 `app/models/user.rb` 에 가서 아래와 같이 입력합니다.

```ruby
class User < ApplicationRecord
    has_secure_password
    
    validates :email, presence: true, uniqueness: true
end
```

암호화를 위한 작업이었습니다. 그리고 이제 회원가입을 할 때 비밀번호와 비밀번호 확인의 값이 같지 않으면 회원가입이 되지 않는 것을 확인할 수 있습니다. 이제 로그인 구현을 하겠습니다.


댓글

이 블로그의 인기 게시물

부트스트랩 사용 시 버튼 오른쪽 정렬하는 방법 (How to use float-right for right align in bootstrap)

맥(Mac)에서 MySql 사용 시 Error: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) 오류가 발생하는 경우 해결 방법

HTML, CSS - footer fixed (foot 하단 고정 시키기)