React + Rails初心者の歩み 〜初級編：超基礎アプリ開発⑤〜

# はじめに
今回は問題集：初心者編の、
「SessionControllerを作成して、emailとpasswordでログインできるAPIを実装」
を進めていきます。

# Rails
まずは、セッションコントローラーを作成します。
この「セッション」はログイン状態の管理・判定を行う機能になります。
```
rails g controller Sessions
```
できたコントローラーに記述していきます。
```
# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def create
    user = User.find_by(email: params[:email])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      render json: {logged_in: true,user: user}
    else
      render json: {logged_in: false,errors: ['Invalid credentials']},status: 401
    end
  end

  def destroy
    session.delete(:user_id)
    render json: {logged_out:true}
  end

  def auth_check
    if session[:user_id]
      user = User.find(session[:user_id])
      render json: {logged_in: true, user: user}
    else
      render json: {logged_in: false}
    end
  end
end
```
1つ1つ解説していきます。

## create
createはフロント（React）側から送られてくるメールアドレス・パスワードによるログインリクエストに対応する処理になります。
```
def create
    user = User.find_by(email: params[:email])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      render json: {logged_in: true,user: user}
    else
      render json: {logged_in: false,errors: ['Invalid credentials']},status: 401
    end
end
```

### user = User.find_by(email: params[:email])
送られてきたメールアドレス（params[:email]）で、
User.find_by()を使ってデータベース内で一致するメールアドレスを探しています。
find_by内のemail:はデータベース内のメールアドレスのカラム（email:）を指定しています。

### if user&.authenticate(params[:password])
パスワードを照合する記述です。
「user&.」は「ユーザーが見つかっていれば（nilでなければ）」という意味になります。
「authenticate()」は、has_secure_password（Userモデルで宣言済み）によって提供されるメソッドで、ハッシュ化されたパスワードと照合して認証します。
パスワードが正しければそのユーザーオブジェクトを返し、間違っていればfalseを返します。

### session[:user_id] = user.id
ログインに成功した場合、Railsのセッションに「user_id」を保存します。
Userコントローラーを追加した時にも使いましたね。
これにより、その後のリクエストでもこのユーザーがログイン済みであることをRails側で判断できます（クッキーに保存される）。

### render json: {logged_in: true,user: user}
JSON形式でログイン成功をフロントエンドに返します。
Userでは「status: 'created'」でしたが、
ログインの時は「logged_in: true」になります。

:::note info
user情報も返していますが、必要な情報だけを返すように制限するのがセキュリティ的にはおすすめ
（例：Serializerやonly: [:id, :email]などを使う）。
By GPTえもん
:::

### render json: {logged_in: false,errors: ['Invalid credentials']},status: 401
ユーザーが見つからない、またはパスワードが間違っている場合に実行されます。
「logged_in: false」とエラーメッセージを返しています。
「status: 401」はHTTPステータスコードで「Unauthorized（認証失敗）」を意味します。

## destroy
destroyはログアウト機能になります。
今回はログアウト機能の実装はしない為、React側では特に実装しませんが、
この先実装予定なのでRails側では作成しておきます。
```
def destroy
    session.delete(:user_id)
    render json: {logged_out:true}
end
```

### session.delete(:user_id)
これはセッション（ログイン情報）から「:user_id」を削除する処理です。
具体的には、サーバー側に保存されているログイン中のユーザーIDを削除することで、「ログアウト状態」にします。
ユーザーがログイン中かどうかは「session[:user_id]」によって判定されているので、これを削除すればログアウトになります。

### render json: { logged_out: true }
JSON形式でログアウト成功をフロントエンドに返します。
「logged_out: true」という情報を送ることで、React側で状態を更新したり、ログインページにリダイレクトしたりするのに使えます。

## auth_check
auth_checkは現在ログインしているかどうかを確認するための処理になります。
```
def auth_check
    if session[:user_id]
      user = User.find(session[:user_id])
      render json: {logged_in: true, user: user}
    else
      render json: {logged_in: false}
    end
end
```
### if session[:user_id]
ユーザーがログイン中かどうかを判断しています。
createでuser_idを保存したやつですね。
「session[:user_id]」に値が入っていれば、ログイン済みになります。

### user = User.find(session[:user_id])
user_idを使ってusersテーブルからユーザー情報を探す処理です。
これによってログイン中のユーザー情報を取得しています。
ユーザー名を表示したりする為ですね。

### render json: {logged_in: true, user: user}
ログインしている場合は、「logged_in: true」を返して、
ユーザー情報も一緒に返しています。
これを使ってフロントエンド側では誰がログインしているかを判断できます。

### render json: {logged_in: false}
ログインしていない場合は「logged_in: false」を返します。

## routes
最後にroutesを記述していきます。
```
# config/routes.rb
post "/login" => "sessions#create"
delete "/logout" => "sessions#destroy"
get "/auth_check" => "sessions#auth_check"
```

以上がRails側の処理になります！
ではReactに行ってみよう！

# React
ではLoginForm.jsxというコンポーネントを作成して、処理を書いていきます。
```
function LoginForm() {
  const [email,setEmail] = useState('')
  const [password,setPassword] = useState('')
  const [message,setMessage] = useState('')

  const handleLogin = async(e)=> {
    e.preventDefault();
    try {
      const res = await axios.post('http://127.0.0.1:3000/login',
        {
          email,password
        },
        {
          headers: {
            "Content-Type": "application/json"
          },
          withCredentials: true
        }
      )
      setMessage('ログイン成功！');
    }catch(err){
      setMessage('ログイン失敗');
    }
  }
  return (
    <>
      <form onSubmit={handleLogin}>
        <input
            type="email"
            name="email"
            value={email}
            onChange={e=>setEmail(e.target.value)}
            placeholder="email"
        />
        <input
            type="password"
            name="password"
            value={password}
            onChange={e=>setPassword(e.target.value)}
            placeholder="password"
        />
        <button className="btn" type="submit">ログイン</button>
        <p>{message}</p>
      </form>
    </>
  )
}

export default LoginForm
```
1つ1つ解説していきます。

## stateの定義
今回は「メールアドレス」「パスワード」「エラーメッセージ」のstateを定義しています。

## e.preventDefault()
フォーム標準の再読み込み動作を防ぎます。

## handleLogin
JSONのやりとりについては省略。
```
const res = await axios.post('http://127.0.0.1:3000/login',
```
データを送信しますので、「get」ではなく「post」。
送信先は「http://127.0.0.1:3000/login 」に指定しています。
```
email,password
```
送信するデータです。

## 出力
```
return (
    <>
      <form onSubmit={handleLogin}>
        <input
            type="email"
            name="email"
            value={email}
            onChange={e=>setEmail(e.target.value)}
            placeholder="email"
        />
        <input
            type="password"
            name="password"
            value={password}
            onChange={e=>setPassword(e.target.value)}
            placeholder="password"
        />
        <button className="btn" type="submit">ログイン</button>
        <p>{message}</p>
      </form>
    </>
)
```
一般的なログインみたいな感じにはこの時点でなっていないですが、
基本的なユーザー登録・ログインの処理自体はできています。
以上で完了！！！！

# まとめ
前回のユーザー登録から今回のログイン処理については、
しっかりと読み込んで処理の記述になれていきたい所存でございます。
めちゃくちゃよく使う基本的な処理ですもんね。
読み込んで手を動かすのみ！

俺の手よ動けぇぇぇぇぇぇぇぇ・・・・！

ではまた。

https://qiita.com/tukaji/items/649d7dc3e0d43d597250