## 루비 온 레일즈에서 devise gem 없이 로그인 기능 구현하기 4, 마지막 (How to build authentication system without devise gem in Ruby on Rails)
쿠키, 세션을 이용해 로그인 기능을 구현해봤습니다.([지난 포스트 참고](http://ghkdgh2365.blogspot.com/2020/07/devise-gem-3-how-to-build.html)) 이번엔 JWT(JSON Web Token)를 이용해 로그인을 구현하겠습니다. 구현 방법은 로그인 요청이 왔을 때, 로그인에 성공하면 서버에서 토큰을 생성한 뒤 클라이언트(브라우저)에게 전달합니다. 이후 클라이언트가 유저에 대한 정보를 요청해야할 때, 받은 토큰을 가지고 서버에 요청해 서버가 토큰 정보를 통해 해당 유저에 대한 정보를 제공합니다. 참고로 로드밸런싱처럼 서버를 확장하거나, 모바일 앱에서 인증을 확인하는데 있어서, 이전 방법보다 JWT가 훨씬 유용하기 때문에, JWT를 이용한 로그인을 많이 사용하고 있는 추세입니다.
그럼 이제 JWT를 이용해 로그인을 구현해보도록 하겠습니다. `개발환경 Ruby 2.7.0 / Rails 6.0.2.2`
JWT을 만들기 위해 gem 을 설치하겠습니다. `Gemfile` 파일에 아래와 같이 추가합니다.
```
# JWT 생성 및 사용
gem 'jwt'
```
추가했다면 저장하고 터미널에 아래와 같이 명령어를 실행합니다.
```
bundle install
```
설치가 끝나면 `lib` 디렉토리에 가서 `json_web_token.rb` 이라는 파일을 만들고 아래와 같이 코드를 입력해줍니다. 생성된 JWT를 해석할 때 사용됩니다.
```
class JsonWebToken
def self.decode(token)
return HashWithIndifferentAccess.new(JWT.decode(token, Rails.application.credentials.secret_key_base)[0])
rescue
nil
end
end
```
그 다음 JsonWebToken.decode()을 사용할 수 있도록 `config/initializers` 에 가서 `jwt.rb` 파일을 만든 뒤 아래와 같은 코드를 입력합니다.
```
require 'json_web_token'
```
이로써 서버가 실행될 때 해당 코드가 불리면서 JsonWebToken에 있는 메소드를 사용할 수 있습니다. jwt 테스트를 위해 아래와 같이 해봅니다.
먼저 토큰을 만들어보겠습니다. 아래와 같이 첫 번째 줄에 token을 만드는 코드를 입력하고 token을 호출해보면 token이 생성된 것을 확인할 수 있습니다.
```
token = JWT.encode({user_id: 1, exp: 1.month.from_now.to_i}, Rails.application.credentials.secret_key_base)
token
=> "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE2MDA3NjA2OTV9.CdcbNuCc2eyqszCa_amlZtroSUybd7XYzk8G-ilsybQ"
```
이제 다시 token을 decode해보겠습니다. 아래와 같이 decode를 사용해서 token의 정보를 확인할 수 있습니다.
```
user_info = JsonWebToken.decode(token)
user_info
=> {"user_id"=>1, "exp"=>1600760695}
```
이러한 원리를 이용해서 로그인, 로그아웃을 구현해보겠습니다.
먼저 이전과 같이, controller를 만들어줍니다. `app/controllers`에 `auth_controller.rb` 라는 controller 파일을 만들어준 뒤 아래와 같이 입력합니다. 코드를 잘 보면 이전 `세션 로그인, 세션 로그아웃` 과 비슷한 것을 볼 수 있습니다. 세션 부분에 token을 만들어 넣어주는 점이 다른 부분입니다.
```
class AuthController < ApplicationController
def sign_in
end
def verify
if params[:email].present? && params[:password].present?
user = User.find_by(email: params[:email])
if user
if user.authenticate(params[:password])
session[:auth_token] = JWT.encode({user_id: user.id, exp: 1.month.from_now.to_i}, Rails.application.credentials.secret_key_base)
flash[:notice] = "로그인 성공"
redirect_to root_path
return
else
flash[:notice] = "잘못된 비밀번호 입니다. 다시 확인해주세요."
end
else
flash[:notice] = "잘못된 이메일입니다. 다시 확인해주세요."
end
else
flash[:notice] = "이메일, 비밀번호 모두 입력해주세요"
end
redirect_to auth_sign_in_path
end
def sign_out
if current_user.present?
reset_session
flash[:notice] = "jwt 로그아웃 성공"
redirect_to root_path
end
end
end
```
다음 `routes.rb` 에 가서 아래와 같이 추가합니다.
```
get 'auth/sign_in'
post 'auth/verify'
delete 'auth/sign_out'
```
그리고 `app/views`에 `auth` 폴더를 만들고 `sign_in.html.erb` 파일을 만들어준 뒤, 아래 코드를 입력합니다.
```
jwt 로그인
<%= form_with(url: "/auth/verify") do |f|%>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit "로그인" %>
<% end %>
```
그 다음 `app/views/home/index.html.erb` 파일에 jwt 로그인 링크와 로그아웃 링크를 추가로 아래와 같이 입력해줍니다.
```
Hello World !
<% if current_user.present? %>
<%= link_to "쿠키 로그아웃", cookie_sign_out_path, method: :delete %>
<%= link_to "세션 로그아웃", sessions_sign_out_path, method: :delete %>
<%= link_to "jwt 로그아웃", auth_sign_out_path, method: :delete %>
<% else %>
<%= link_to "쿠키 로그인", cookie_new_path %>
<%= link_to "세션 로그인", sessions_sign_in_path %>
<%= link_to "jwt 로그인", auth_sign_in_path %>
<% end %>
<%= link_to "회원가입", new_user_path %>
```
마지막으로 로그인이 성공적으로 되었다면, 로그인이 되었다는 것을 알려줄 수 있도록 `app/controllers/application_controller.rb` 에 아래와 같이 추가합니다.
```
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
if session[:auth_token].present?
user_info = JsonWebToken.decode(session[:auth_token])
if user_info.present?
@current_user = User.find(user_info[:user_id])
end
end
@current_user ||= User.find_by(email: cookies[:email], password_digest: cookies[:password]) || User.find_by(id: session[:user_id])
end
end
```
기존과 달라진 점은 아래 부분이 추가 되었습니다. 로그인에 성공하면 `session`에 `:auth_token` 이라는 것을 넣어주고 `session[:auth_token]` 이 있는지 물어본 뒤 있다면 auth_token의 값을 가지고 유저 정보를 조회해 로그인 유무를 확인하는 것입니다.
```
if session[:auth_token].present?
user_info = JsonWebToken.decode(session[:auth_token])
if user_info.present?
@current_user = User.find(user_info[:user_id])
end
end
```
이로써 드디어, devise gem 없이 로그인 기능을 Cookie, Session, JWT(JsonWebToken)으로 구현해봤습니다. 바빠서(~~네 핑계 맞습니다.~~) 참 오래걸렸는데, 다 끝내니 마음이 개운하네요. 혹시 수정사항, 개선사항이 있다면 언제든 피드백 주시면 감사하겠습니다.
---
참고 : https://developer2080.tistory.com/2 / https://kbs4674.tistory.com/89?category=822778 / https://www.slideshare.net/JunghyunPark39/api-4-api
댓글
댓글 쓰기