はじめに
- Shopifyストアのカスタマーがログインできる、Next.jsのクライアントアプリを提供することにした。
- SSOを実現したかったが、Shopify Plus契約が予算的に難しいため、Auth0によるパスワードレスログインを実装することにした。
Auth0 の設定
- Auth0 ダッシュボードにログインし、パスワードレスログインを有効にします。
- Authentication > Passwordless に移動し、Email オプションを有効にします。
ソース
passwordless-login-page
メールアドレスを入力し、Auth0へ送信するフォームを持つページ
'use client';
import { useState } from 'react';
import Image from 'next/image'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import AppleIcon from '@/assets/icons/apple.svg'
import GoogleIcon from '@/assets/icons/google.svg'
import LineIcon from '@/assets/icons/line.svg'
import LockIcon from '@/assets/icons/lock.svg'
import ProfileIcon from '@/assets/icons/profile.svg'
import { FormattedMessage, useIntl } from 'react-intl'
export default function PasswordlessLoginPage() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch('/passwordless-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (res.ok) {
setMessage('ログインリンクがメールに送信されました!');
} else {
setMessage('エラーが発生しました。もう一度お試しください。');
}
};
return (
<>
<form className="mt-4">
<div className="flex flex-col gap-6">
<Input
type="email"
placeholder="Your email"
icon={<ProfileIcon width={14} />}
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mt-10 flex justify-center">
<Button
size="lg"
type="button"
className="w-48"
data-testid="login-button"
onClick={handleLogin}
>
<FormattedMessage id="general.button_login" />
</Button>
</div>
</form>
{message && <p>{message}</p>}
</>
);
}
passwordless-login
フォームからメールアドレスを受取、Auth0のAPIへ送信する処理
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
// 動的生成を強制
export const dynamic = 'force-dynamic';
export async function POST(req: NextRequest) {
const { email } = await req.json();
try {
const response = await fetch(`https://${process.env.NEXT_PUBLIC_AUTH0_DOMAIN}/passwordless/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID,
connection: 'email',
send: 'link',
email,
}),
});
if (response.ok) {
return NextResponse.json({ message: 'ログインリンクを送信しました' });
} else {
return NextResponse.json({ message: 'エラーが発生しました' }, { status: response.status });
}
} catch (error) {
return NextResponse.json({ message: 'サーバーエラーが発生しました' }, { status: 500 });
}
}
passwordless-login-validate
Auth0からのコールバック
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { useAccessToken } from '@/lib/use-access-token'
import { useUserInfo } from '@/lib/use-user-info'
export default function ValidateLogin() {
const router = useRouter()
const [, setAccessToken] = useAccessToken()
useEffect(() => {
const hashParams = new URLSearchParams(window.location.hash.substring(1));
const accessToken = hashParams.get('access_token');
if (accessToken) {
setAccessToken(accessToken)
console.log("Access Token: ", accessToken);
router.replace('/home')
} else {
router.replace('/top-not-login')
}
}, [router, setAccessToken]); // Add dependencies for useEffect
return null;
}
成功時の動作
- フォームにメールアドレスを入力し、送信する
- Auth0からのメールを受信する
- メール上のボタンをクリックする
- アプリへのアクセスを認可する
- /homeへリダイレクトされる