본문 바로가기
Ruby on Rails

Ruby On Rails 6.0.0 InvalidAuthenticityToken 에러 대처하기

by 혜리루 2019. 10. 17.

최근 회사 업무때문에 Ruby On Rails를 학습하고 있습니다.

처음 접하는 언어/프레임워크라 간단한 CRUD 튜토리얼을 따라하는 중인데요, 거의 뭐 프로젝트를 시작하자 마자 빨간색 에러 화면을 만나게 되었습니다.

 

form 태그를 통해서 POST 메소드로 데이터를 전달하는 부분에서 난 에러인데요

대충 보니 인증 Token이 유효하지 않다는 것 같습니다. 

 

입문 강의였기 때문인지 강의에서는 application controller에서 이 코드를 주석처리 하면 된다고 하고 대충 넘어갔습니다.

 

눈 씻고 봐도 그런 코드가 없네요..

 

어쩔 수 없이 에러가 난 이유를 하나씩 찬찬히 찾아보았습니다.

 

먼저 Rails의 Authenticity Token이 뭐 하는 놈인지 찾아보았습니다.

 


Authenticity Token 은 rails가 ' 사이트 간 요청 위조 (CSRF 또는 XSRF) 공격' 을 막는 방법입니다.

먼저 authenticity_token은 사용자의 세션에 저장됩니다. 그리고 사용자가 GET 이외의 PUT / POST / DELETE (내용을 수정할 수있는 메소드) 요청을 하면 사용자가 제출하는 form의 숨겨진 필드에도 authenticity_token이 저장됩니다.

서버는 액션을 실행하기 전에 폼 안의 authenticity_token과 세션에 저장된 authenticity_token를 비교해서 유효성을 검증합니다. 두 토큰이 같지 않으면 요청을 실행하지 않습니다.


 

위 에디터 상의 주석을 읽어보니 'protect_from_forgery with: :exception' 가 이 authenticity token을 처리하는 코드였습니다.

따라서 이 부분을 주석처리 하게 되면 authenticity token 검사를 하지않게 되고, 오류가 뜨지않겠죠. 하지만 실제 운영중인 어플리케이션에서 그냥 이 코드를 생략해버린다면 csrf 공격으로 부터 취약해지게 될 수도 있습니다...

 

만 주석처리를 하고 자시고 할 것도 없이 제 코드에는 이런 문장이 전혀 없는데요

rails 버전이 업그레이드 되면서 뭔가가 달라진 모양입니다...

그래서 rails 6.0.0 공식 문서를 찾아보았습니다.

 


All requests are checked except GET requests as these should be idempotent. Keep in mind that all session-oriented requests are CSRF protected by default, including JavaScript and HTML requests.


 

레일즈 6.0에서는 authenticity token을 이용해 CSRF 공격을 막는 부분이 디폴트로 설정이 되어있었습니다.

따라서 위의 방식대로 오류를 없애려면 반대로 이 디폴트 설정을 바꿔주는 코드를 추가해야 합니다.

 

대처방법 1


If you're building an API or an SPA you could change forgery protection method in ApplicationController (by default: :exception):

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end

We may want to disable CSRF protection for APIs since they are typically designed to be state-less. That is, the request API client will handle the session for you instead of Rails.


 

공식 문서에서는 위처럼  protect_from_forgery 메소드를 바꾸어줄 수 있다고 합니다. 내부 리소스의 상태 변경이 없는 api등을 개발하는 경우에는 위처럼 application controller에 코드를 추가해서 Authenticity Token 검사를 생략할 수 있습니다. 

 

대처방법 2

그 외에 실제로 POST, PUT, DELETE 등을 통해 상태변경이 이루어지는 경우라면 실제로 form 안에 authenticity token을 넣어줘야 합니다. 이때 rails에서 제공하는 폼헬퍼(form_tag)를 사용할 수 있습니다. 폼헬퍼를 사용하면 자동으로 authenticity token을 추가하는 태그가 폼태그 안에 생성됩니다.

<%= form_tag "/users/delete", method: :post do %>
<input type="submit">회원 탈퇴하기</input>
<% end %>

 

이 태그는 아래와 같이 변환이 됩니다.

<form action="/users/delete" method="get">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="Emq8ABYNVXEJAvJ1Z0BCnmD+dI69AHiMQ/gdTyuSMU4eNxPMWMxXNO4cTTTua/zZ1xDQ9uhy/6NyBXEwBeWjQQ==" />
<input type="submit">회원 탈퇴하기</input>
</form>

대처방법 3

혹은 폼 안에 직접 Authenticity Token을 넣어줄 수도 있습니다.

<form method="post" action="/create">
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
제목 <input type="text" name="title"/> <br>
내용 <input type="text" name="content"/>

<input type="submit">
</form>

 

대처방법 4

일반폼이 아니라 dynamic form (동적폼)을 사용하는 경우에는  렌더링 하는 모든 html 파일의 head 부분에 csrf_meta_tags를 추가해주면 됩니다.

 


The token parameter is named authenticity_token by default. The name and value of this token must be added to every layout that renders forms by including csrf_meta_tags in the HTML head.

csrf_meta_tags returns meta tags “csrf-param” and “csrf-token” with the name of the cross-site request forgery protection parameter and token, respectively.

<head>
<%= csrf_meta_tags %>
</head>

These are used to generate the dynamic forms that implement non-remote links with :method.


 

어휴 create 액션 하나 만드는데 굉장히 오래걸렸네요.

저와 같은 오류에 봉착하신 분이라면 위의 방법들 중에서 상황에 맞는 방법을 골라서 쓰시면 될 것 같습니다.


출처: https://flearning-blog.tistory.com/42 [플러닝 Flearning]

https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html

https://api.rubyonrails.org/classes/ActionView/Helpers/CsrfHelper.html

댓글