【Next.js × Rails7】NextAuthでのアカウント情報をDBに保存したい

## 概要
おつかれさまです！
前回はNextAuth.jsでログイン機能を実装しました。
今回はRailsのAPIと連携し、アカウント情報をDBに保存するような実装をまとめていきます！

https://qiita.com/keisuke_sakuma/items/4b56b9e81c1788d38440

## 実装方針
1. Rails側にUserモデルを作成
1. ルーティングの設定
1. コントローラとリクエスト時の処理を実装
1. CORSの設定
1. Next側でログイン後にリクエストを送るように実装
1. Rails側でデータの確認


## Rails側にUserモデルを作成
まずNext側でどのようにアカウント情報を保持しているかというと、下記の`callbacks`のバリューである`signIn()`フックを実行したときにコールバックで変数を保持できるようにできます。
今回は`provider`、`uid`、`name`、`email`をDB側に登録できるように設定していきます。

```javascript:src/app/api/auth/[...nextauth]/route.ts
const handler = NextAuth({
	secret: process.env.NEXTAUTH_SECRET,
	providers: [
		GoogleProvider({
			clientId: process.env.GOOGLE_CLIENT_ID ?? '',
			clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '',
		}),
	],
	callbacks: {
		async signIn({ user, account }) {
			const provider = account?.provider;
			const uid = user?.id;
			const name = user?.name;
			const email = user?.email;
		},
```
モデルの生成コマンドを実行し、マイグレーションファイルに追加します。
```ruby
rails g model User
```

```ruby:db/migrate/xxxxx.rb
class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string "provider", null: false
      t.string "uid", null: false
      t.string "name", null: false
      t.string "email", null: false
      t.timestamps
    end
  end
end
```
マイグレーションを実行します。
```ruby
rails db:migrate
```

## ルーティングの設定
ルーティングを設定します。`:provider`にすることによって、今後Google以外のログインにも対応できるようにしています。
```ruby:config/routes.rb
Rails.application.routes.draw do
  post 'auth/:provider/callback', to: 'api/v1/users#create'
end
```
## コントローラとリクエスト時の処理を実装
コントローラを作成します。
```ruby
rails g controller Api::V1::users
```
createアクションに処理を書いていきます。
`find_or_create_by`を使って、新規レコードであれば保存し、既に存在しているのであればインスタンスを返すようにしています。
```ruby:app/controllers/api/v1/users_controller.rb
module Api
  module V1
    class Api::V1::UsersController < ApplicationController
      def create
        # 条件に該当するデータがあればそれを返す。なければ新規作成
        user = User.find_or_create_by(provider: params[:provider], uid: params[:uid], name: params[:name], email: params[:email])                      
        if user
          head :ok
        else
          render json: { error: "ログインに失敗しました" }, status: :unprocessable_entity
        end
      rescue StandardError => e
        render json: { error: e.message }, status: :internal_server_error
      end
    end
  end
end
```
## CORSの設定
CORSについてはこの記事がわかりやすかったです。

https://zenn.dev/syo_yamamoto/articles/445ce152f05b02

今回のケースだと、Next.jsのURL`（http://localhost:3000）`とRailsのURL`（http://localhost:3001）`は別々のアプリケーションであるため、この２つの通信を許可する作業をしていきます。

Gemfileに`rack-cors`を追加して`bundle install`します。
その後、`cors.rb`がコメントアウトになっている部分を外し設定します。
```ruby:Gemfile
# for CORS
gem "rack-cors"
```

```ruby:config/initializers/cors.rb
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    #許可したいURLを設定
    origins "http://localhost:3000"
    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

```
## Next側でログイン後にリクエストを送るように実装
API側で実装が確認できたので、Next.jsでリクエストの処理を実装していきます。
`NEXT_PUBLIC_API_URL`でrails側のオリジン`(http:/localhost:3001)`を設定しています。


```javascript:src/app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import axios from 'axios';

const apiUrl = process.env.NEXT_PUBLIC_API_URL;

const handler = NextAuth({
	secret: process.env.NEXTAUTH_SECRET,
	providers: [
		GoogleProvider({
			clientId: process.env.GOOGLE_CLIENT_ID ?? '',
			clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? '',
		}),
	],
	callbacks: {
		async signIn({ user, account }) {
			const provider = account?.provider;
			const uid = user?.id;
			const name = user?.name;
			const email = user?.email;
			try {
				const response = await axios.post(
					`${apiUrl}/auth/${provider}/callback`,
					{
						provider,
						uid,
						name,
						email,
					}
				);
				if (response.status === 200) {
					return true;
				} else {
					return false;
				}
			} catch (error) {
				console.log('エラー', error);
				return false;
			}
		},
	},
});
export { handler as GET, handler as POST };

```
## Rails側でデータの確認
コンソールで確認するとデータの登録が確認できました！
![スクリーンショット 2023-09-10 16.55.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/6b6d25da-0b81-2a7d-8b06-39107a08e59b.png)

## まとめ
認証まわりが自信なかったので勉強できてよかったです！
最後まで読んで頂きありがとうございました！