事の始まり
( ^o^)「Railsチュートリアルに沿ってログイン機能を実装するぞ」
( ^o^)「なるほど…Railsのsessionメソッドを使うんだな…」
(; ^o^)「…ReactとRailsでcookiesのやり取りってどうするんだ!?」
現在、RailsとReactを用いてSPAの作成に挑戦中です。ログイン機能を実装する際に、cookiesのやり取りやsessionメソッドで作成されたcookiesなどに対して、疑問を抱いたので記事に残そうと思います。
開発環境
Mac OS Big Sur 11.6 (M1)
VSCode
Docker / Docker-Compose
Ruby 3.0.3p157
Rails 6.1.4.4
React 17.0.2
MySQL 8.0
この記事で紹介すること、しないこと
すること
axiosを使用したRailsとReactとのcookiesのやり取りなど。
しないこと
環境構築の方法、RailsでCORSを許可するためのrack-corsの導入などについては紹介しません。
やりたいこと
Railsチュートリアルでは、Railsのsessionメソッドで生成した一時cookiesでログイン機能を実装していきます。
Railsでviewまで実装する場合は、Railsと接続しているブラウザでcookiesを保存しますが、今回はReactでSPA化するのでReact側のブラウザでcookiesを保存する必要性が出てきます。
__Railsから受け取ったcookiesをReact側のブラウザで保存する__ことを目標とします。
1. cookiesを受け取れるのか
まずは、「どのようにすればRailsとReact間でcookiesのやり取りができるのか」について調べてみました。どうやら、axiosに { withCredentials: true }とオプションをつけることでcookiesを送ることができるようになるみたいです。
以下の例は, "localhost:3001/api/login"に対して{ session }という形式でログイン情報をPOSTし、ログインできるか試みる記述になります。通信が成功したかどうかはconsoleで確認します。
const DEFAULT_API_LOCALHOST: string = 'http://localhost:3001/api'
const new_session: string = `${DEFAULT_API_LOCALHOST}/login`
axios.post(new_session, { session }, { withCredentials: true })
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
})
Rails側ではログインできた場合にsessionを作成します。
def create
user = User.find_by(email: params[:session][:email].downcase)
# userが有効かつ、パスワードが正しいか
if user&.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] ? remember(user) : forget(user)
render json: {}, status: :ok
else
render json: {}, status: :bad_request
end
end
sessionを作成するログインメソッドです。
# userでセッションを作成する
def log_in(user)
session[:user_id] = user.id
end
実行前
cookiesは何も保存されていません。この状態から、seedで生成した有効なユーザーでログインを実行してみます。
実行後
できちゃいました。consoleには正しく処理ができた証に200番が返ってきています。
…あまりにもあっさりできたので「本当にこれはログインのsessionが保存されているのか!?」と疑ってしまいました。
2. ログアウトでcookieはどうなる?
セッションについてあまり理解していなかったため「ログインでcookiesが作成されるなら、ログアウトでcookiesが消えるだろう!」と安直な仮定を立ててしまいました。
今度は、"localhost:3001/api/logout"に対してDELETEをリクエストします。
const delete_session: string = `${DEFAULT_API_LOCALHOST}/logout`
axios.delete(delete_session , { withCredentials: true })
.then(response => console.log(response))
.catch(error => console.log(error))
sessionsコントローラーは以下のとおりです。sessions.delete(:user_id)でcookies内のuser_idに関するデータを削除します。
def destroy
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
ログアウトの実行結果
先程に比べて少々サイズが減り、値も変わったcookiesが残っています。
「ログアウトでcookiesが消えるだろう」と考えていたため、「cookiesが残っている→ログアウトできていない?」と更に仮定しました。
3. 果たしてログアウトできていたのか?
先程の処理ではログアウトできていないかもしれない…と不安に駆られたため、もう一つ実験してみることにしました。
- ログインする→ユーザー情報を取得できる(ログインしているから取得できるべき)
- ログアウトする→ユーザー情報を取得できない(ログアウトしているなら取得できないはず)
以上の処理を実行し、結果を確かめてみます。
ログイン→ユーザー情報を取得
Railsに処理を追加します。logged_in?メソッドはログインしている場合にtrueを返します。
logged_inアクションに対してGETメソッドを送り、ログインしているならユーザー情報を、ログインしていないなら400番(bad_request)を返す動作を期待します。
def logged_in
if logged_in?
render json: { user: current_user }
else
render json: {}, status: :bad_request
end
end
Reactでは、ログインしている状態でRailsのlogged_inアクションに対してGETリクエストを送信します。
const loggedIn: string = `${DEFAULT_API_LOCALHOST}/logged_in`
axios.get(loggedIn , { withCredentials: true })
.then(response => console.log(response))
.catch(error => console.log(error))
実行結果
新たにログインしたためcookiesのサイズが変わっています。console.logの1つ目にはログイン成功時のレスポンスが表示されています。
2つ目のconsole.logには現在ログインしているテストユーザーの情報が表示されています。
ログアウト→ユーザー情報を取得できない(はず)
今度はログアウトした状態で実験してみます。
実行結果
ログアウトした証拠にcookiesのサイズと値が変わっています。また、console.logにはRailsから返ってきた400番が表示されています。
期待通り、ログアウトできていることが証明できました!
感想
最後に残ったcookiesにはどのようなデータが含まれているか気になります。こちらに関しては引き続き調べてみようと思います。
自作のアプリをSPA化するにあたり、Railsだけで作成していたときには気づかなったことや疑問点がたくさん生まれました。これからも試行錯誤しながら成長していきたいです
参考にさせていただきました
REFFECT様
axiosだけでなく、基礎の基礎からわかりやすく解説されています。見やすくておすすめです。
Reactを使ってaxiosを学ぶ
TechWiki様
自動翻訳なのでしょうか?少し日本語が読みづらいですが、 withCredntialsの設定方法について勉強になりました。
すべてのAxiosリクエストに資格情報を強制する方法