前回の続きです.
Auth.jsの基礎 (Google OAuthを使用)
今回,Auth.jsにより取得したログイン情報をバックエンドに渡しユーザ管理する手法として以下のプロセスを要します.
Googleの認証が成功するとAuth.jsがaccount.id_tokenを受け取る.このID トークンはGoogleが発行するJWTである.
account.id_tokenはjwtコールバックで token.idTokenに保存する.
セッションにIDトークンを保存する.
セッションからIDトークンを取得し,fetch()
を用いてバックエンドに送信.
バックエンドでIDトークンを検証,成功後に初めてデータを処理できる.
このように,結構面倒な処理をする必要があります.(コード自体はそこまで書かない気がする)
Auth.jsでは,account.id_tokenは定義されているものの,tokenとsessionにid_tokenは定義されていません.しかしそれでもid_tokenを渡したいので,定義します.
import "next-auth/jwt" ;
declare module "next-auth/jwt" {
interface JWT {
idToken ?: string ;
}
}
declare module "next-auth" {
interface Session {
user : {
idToken ?: string ;
} & DefaultSession ["user" ];
}
}
Tip
session.idToken
でいいかなと思っていたけど,V5ではsession.user.idToken
にすることが推奨されていたのでこのようにしてます.
import NextAuth from "next-auth" ;
import Google from "next-auth/providers/google" ;
export const { handlers, signIn, signOut, auth } = NextAuth ({
providers : [Google ],
callbacks : {
async jwt ({ token, account } ) {
if (account) {
token.idToken = account.id_token ;
}
return token;
},
async session ({ session, token } ) {
session.user .idToken = token.idToken ;
return session;
},
},
});
こうすることで,auth()
からuser.idToken
を取得することができるようになります.
トークン情報はbodyではなくheaderに含めるのが一般的です.http://localhost:3001/auth/google
に渡すように設定します.
import { auth } from "@/auth" ;
import { redirect } from "next/navigation" ;
import React from "react" ;
export default async function DashboardLayout ({
children,
}: {
children: React.ReactNode;
} ) {
const session = await auth ();
if (session) {
const idToken = session.user .idToken ;
if (idToken) {
await fetch ("http://localhost:3001/auth/google" , {
method : "POST" ,
headers : {
"Content-Type" : "application/json" ,
Authorization : `Bearer ${idToken} ` ,
},
});
}
} else {
redirect ("/signin" );
}
return <> {children}</> ;
}
rails new backend --api -T
今回使うgemは以下の通りです.
gem 'rack-cors' , require: 'rack/cors'
gem "google-id-token"
gem 'dotenv-rails'
bundle install
を忘れずに実行しましょう
http://localhost:3000
を許可しましょう
Rails .application.config.middleware.insert_before 0 , Rack : :Cors do
allow do
origins "http://localhost:3000"
resource "*" ,
headers: :any ,
methods: [:get , :post , :put , :patch , :delete , :options , :head ]
end
end
3000番は既にフロント側で使用されているので,バックエンド側は3001番に設定します.
port ENV .fetch("PORT" , 3001 )
google_sub
はユーザーごとに一意なIDを示しており,IDトークンの検証時に取得できます(フロントエンドでも取得できる).
rails generate model User google_sub:string name:string email:string picture:string
rails db:migrate
を忘れずに実行しましょう
作成したUser
モデルのgoogle_sub
とemail
について,それぞれ必須かつユニークな値にバリデートします.
class User < ApplicationRecord
validates :google_sub , presence: true , uniqueness: true
validates :email , presence: true , uniqueness: true
end
rails generate controller sessions
実行すると,sessions_controller.rb
が生成されるので,そのファイルを編集していきます.
class SessionsController < ApplicationController
require "google-id-token"
def google_auth
auth_header = request.headers["Authorization" ]
unless auth_header&.start_with?("Bearer " )
return render json: { error: "Unauthorized" }, status: :unauthorized
end
token = auth_header.split("Bearer " ).last
begin
validator = GoogleIDToken : :Validator .new
payload = validator.check(token, ENV ["GOOGLE_CLIENT_ID" ])
sub = payload["sub" ]
email = payload["email" ]
name = payload["name" ]
picture = payload["picture" ]
user = User .find_or_create_by(google_sub: sub) do |u |
u.name = name
u.email = email
u.picture = picture
end
render json: { message: "Login successful" , user: user }
rescue StandardError => e
Rails .logger.error "Google Auth Error: #{e.message} "
render json: { error: "Invalid ID token" }, status: :unauthorized
end
end
end
auth/google
にアクセスしたときにSessionsController
クラスのgoogle_auth
メソッドが実行されるようにルーティングします.
Rails .application.routes.draw do
get "up" => "rails/health#show" , as: :rails_health_check
post "auth/google" , to: "sessions#google_auth"
end
実際にhttp://localhost:3000
にアクセスし,アカウント作成,ログイン,ログアウトを検証してみましょう.想定通りの挙動がされれば成功です.