Open1
Next.jsのServer ActionsでAWS Cognitoを操作する
Next.jsの最新機能であるServer Actionsと、AWSの認証サービスであるCognitoを組み合わせることで、サーバーサイドで安全にユーザー管理を行えるようになるそう。
Cognitoとは何か?
AWS Cognito(コグニート)は、Webアプリケーションやモバイルアプリのユーザー認証と認可を管理するためのAWSのサービスです。
Cognitoの主な機能
- ユーザープール: ユーザーディレクトリを作成・管理する機能
- IDプール: 一時的なAWS認証情報を発行する機能
- ソーシャルサインイン: Google、Facebook、Amazonなどのソーシャルアカウントでのログイン
- MFA(多要素認証): セキュリティ強化のための2段階認証
Cognitoの基本概念
-
ユーザープール「ユーザーのデータベース」メールアドレスやパスワードなどのユーザー情報を保存・管理
-
アプリクライアントはアプリケーションがCognitoのAPIを呼び出すために必要な識別子
-
ユーザー属性はユーザーに関連付けられた情報(名前、メールアドレス、電話番号など)
Server Actionsとは何か?
Server Actionsは、Next.js 13から導入された機能で、クライアントコンポーネントからサーバー上の関数を直接呼び出すことができる。フォームの送信処理やデータ取得などをサーバーサイドで実行できるため、セキュリティとパフォーマンスが向上
Server Actionsの特徴
- クライアントコンポーネントからサーバー関数を直接呼び出せる
-
'use server'
ディレクティブでサーバーコードを明示的に宣言 - フォームの送信処理などをクライアントJavaScriptなしで実行可能
- クライアントに公開したくない処理やAPIキーを安全に扱える
基本的な書き方(メモ)
'use server'
// この関数はサーバーサイドでのみ実行される
export async function myServerAction(formData: FormData) {
// サーバーサイドの処理
const name = formData.get('name')
// データベース操作やAPIリクエストなど
// 結果を返す
return { success: true, message: `Hello, ${name}!` }
}
Cognitoユーザープールを操作する
AWS Cognitoユーザープールを操作するには、Server Actionsを使うことで安全にAPIキーを扱いながら実装できます。以下、主要な操作方法を解説します。
1. AWS SDKのセットアップ
まず、必要なパッケージをインストールします:
npm install @aws-sdk/client-cognito-identity-provider
2. ユーザー一覧を取得する
'use server'
import {
CognitoIdentityProviderClient,
ListUsersCommand
} from '@aws-sdk/client-cognito-identity-provider'
export type UserInfo = {
email: string
status: string
}
export async function listUsers(): Promise<UserInfo[]> {
const region = process.env.AUTH_COGNITO_REGION ?? 'ap-northeast-1'
const userPoolId = process.env.AUTH_COGNITO_USER_POOL_ID ?? ''
if (!userPoolId) {
throw new Error('ユーザープールIDが設定されていません')
}
try {
// Cognitoクライアントの初期化
const cognitoClient = new CognitoIdentityProviderClient({ region })
// ユーザー一覧取得コマンドの作成
const command = new ListUsersCommand({
UserPoolId: userPoolId,
AttributesToGet: ['email']
})
// コマンドの実行
const response = await cognitoClient.send(command)
// レスポンスからユーザー情報を抽出
return (response.Users || []).map(user => {
// メールアドレスを取得
const emailAttribute = user.Attributes?.find(
attr => attr.Name === 'email'
)
return {
email: emailAttribute?.Value || user.Username || '',
status: user.UserStatus || ''
}
})
} catch (error) {
console.error('ユーザー一覧取得エラー:', error)
throw new Error(
'ユーザー一覧の取得に失敗しました: ' +
(error instanceof Error ? error.message : String(error))
)
}
}
3. 新規ユーザーを登録する
'use server'
import {
CognitoIdentityProviderClient,
AdminCreateUserCommand
} from '@aws-sdk/client-cognito-identity-provider'
export async function registerUser(email: string): Promise<void> {
const region = process.env.AUTH_COGNITO_REGION ?? 'ap-northeast-1'
const userPoolId = process.env.AUTH_COGNITO_USER_POOL_ID ?? ''
if (!userPoolId) {
throw new Error('ユーザープールIDが設定されていません')
}
try {
// Cognitoクライアントの初期化
const cognitoClient = new CognitoIdentityProviderClient({ region })
// ユーザー作成コマンドの作成
const command = new AdminCreateUserCommand({
UserPoolId: userPoolId,
Username: email,
UserAttributes: [
{ Name: 'email', Value: email },
{ Name: 'email_verified', Value: 'true' }
],
DesiredDeliveryMediums: ['EMAIL'] // 仮パスワードをメールで送信
})
// コマンドの実行
await cognitoClient.send(command)
} catch (error) {
console.error('ユーザー登録エラー:', error)
// エラーハンドリング
if (error instanceof Error) {
// ユーザーが既に存在する場合
if (error.name === 'UsernameExistsException') {
throw new Error(`ユーザー「${email}」は既に存在します`)
}
}
throw new Error(
'ユーザー登録に失敗しました: ' +
(error instanceof Error ? error.message : String(error))
)
}
}
フロントエンドとの連携
これらのServer Actionsをフロントエンド(クライアントコンポーネント)から呼び出す方法を見ていきましょう。
ユーザー一覧を表示するコンポーネント
'use client'
import { useState, useEffect } from 'react'
import { listUsers } from '@/app/actions/user-actions'
import type { UserInfo } from '@/app/actions/user-actions'
export function UserList() {
const [users, setUsers] = useState<UserInfo[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function fetchUsers() {
try {
const data = await listUsers()
setUsers(data)
} catch (err) {
setError(err instanceof Error ? err.message : '不明なエラーが発生しました')
} finally {
setLoading(false)
}
}
fetchUsers()
}, [])
if (loading) return <div>読み込み中...</div>
if (error) return <div>エラー: {error}</div>
return (
<div>
<h2>ユーザー一覧</h2>
<table>
<thead>
<tr>
<th>メールアドレス</th>
<th>ステータス</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.email}>
<td>{user.email}</td>
<td>{user.status}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
ユーザー登録フォームコンポーネント
'use client'
import { useState } from 'react'
import { registerUser } from '@/app/actions/user-actions'
export function UserRegistrationForm() {
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setLoading(true)
setError(null)
setSuccess(false)
try {
await registerUser(email)
setSuccess(true)
setEmail('')
} catch (err) {
setError(err instanceof Error ? err.message : '不明なエラーが発生しました')
} finally {
setLoading(false)
}
}
return (
<div>
<h2>新規ユーザー登録</h2>
{success && <div className="success">ユーザーを登録しました。確認メールを送信しました。</div>}
{error && <div className="error">{error}</div>}
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">メールアドレス:</label>
<input
type="email"
id="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? '登録中...' : 'ユーザーを登録'}
</button>
</form>
</div>
)
}
環境設定
AWS Cognitoを使うには、適切な環境変数を設定する必要がある
# Cognito設定
AUTH_COGNITO_REGION=ap-northeast-1
AUTH_COGNITO_USER_POOL_ID=ap-northeast-1_xxxxxxxxx
AUTH_COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx
# AWS認証情報(開発環境用)
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
認証情報の取得方法
Server ActionsでCognitoを操作するためには、AWS SDKが認証情報を取得できる必要があります。AWS SDKは以下の順序で認証情報を探す:
- 環境変数(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
- 共有認証情報ファイル(~/.aws/credentials)
- ECS環境変数(Amazon ECS)
- インスタンスメタデータサービス(EC2インスタンス)