📚
AWS Cognito SDKをNext.js(TypeScript)に組み込んで使ってみる
概要
AWS Cognito の SDK を使用して、Next.js に組み込んでログイン認証機能を作成してみます。
なお、SDK には種類があり、aws-sdk
とamazon-cognito-identity-js
の2つがあります。サーバサイドで実装する場合は、aws-sdk
を用いるのが良いと思います。クライアントサイドで実装する場合は、amazon-cognito-identity-js
が利用できます。
本記事では、クライアントサイドで実装する場合の、amazon-cognito-identity-js
を利用する方法について記載します。
前提とする構成
- Next.js
- TypeScript
- AWS Cognito
- Client Secret は使用しない
- amazon-cognito-identity-js
手順
Congito SDK のインストール
次のコマンドを実行し、Cognito の SDK を Next.js のプロジェクトにインストール(追加)します。
shell
yarn add amazon-cognito-identity-js
SDK を使用するページでの import
SDK を使用するページで、次の import を行います。
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
以上で、Congito User Pool に関する処理を SDK で呼び出せるようになります。
実装例
SDK を用いて、会員登録画面、E-mail 認証画面、ログイン画面を実装した例になります。
前提事項
- 会員登録時に取得する属性情報は、E-mail とパスワードのみ
- E-mail に関しては有効性確認を行う
- ログインは E-mail とパスワードで行える
実装構成
.
├ components
│ └ ConfirmContext.tsx :E-mail認証用に、画面間でE-mailを受け渡す
├ pages
│ ├ _app.tsx :画面間でのE-mail受渡用のContextProvide呼出
│ ├ signup.tsx :登録画面
│ ├ confirm.tsx :E-mail認証画面
│ └ login.tsx :ログイン画面
実装例
components/ConfirmContext.tsx
import { createContext } from "react";
// 画面間でのE-mail値受け渡し用のContextのインターフェースを定義
export interface ConfirmContextType {
email: string;
setEmail: (email: string) => void;
}
// Context初期化
export const ConfirmContext = createContext<ConfirmContextType>({
email: "",
setEmail: () => {},
});
pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { ConfirmContext } from "../components/ConfirmContext";
import { useState } from "react";
function MyApp({ Component, pageProps }: AppProps) {
// E-mail有効性確認用のstateを定義し、画面間で受け渡せるようstateをContextProvideに渡す
const [email, setEmail] = useState<string>("");
return (
<ConfirmContext.Provider value={{ email, setEmail }}>
<Component {...pageProps} />
</ConfirmContext.Provider>
);
}
export default MyApp;
pages/signup.tsx
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { useRouter } from "next/router";
import { useContext, useRef } from "react";
import { ConfirmContext } from "../components/ConfirmContext";
const Signup = () => {
// Contextから画面間でのE-mail値受け渡し用state関数を取得
const { setEmail } = useContext(ConfirmContext);
// 画面内で使用するrefとrouterを定義
const refEmail = useRef<HTMLInputElement>(null);
const refPassword = useRef<HTMLInputElement>(null);
const router = useRouter();
// Cognito User Poolへの接続情報を設定(実際には環境変数などから取得した方が良い)
const poolData = {
UserPoolId: "<UserPoolId>",
ClientId: "<ClientId>",
};
// Cognito User Poolへ接続
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
// SignUpボタン押下時処理
const submitHandler = (e: { preventDefault: () => void }) => {
// E-mail未入力エラー時のハンドリングを実装する
if (!refEmail.current?.value) {
console.log("E-mail is Empty");
return;
}
// パスワード未入力エラー時のハンドリングを実装する
if (!refPassword.current?.value) {
console.log("Password is Empty");
return;
}
// 入力されたE-mailをCognitoに渡す属性情報リストに追加する
let attributeList = [];
let attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute({
Name: "email",
Value: refEmail.current.value,
});
attributeList.push(attributeEmail);
// Cognitoのユーザ登録処理を呼び出す
// username=入力されたE-mail、password=入力されたパスワード、userAttributes=[入力されたE-mail]
userPool.signUp(
refEmail.current.value,
refPassword.current.value,
attributeList,
attributeList,
(err, result) => {
// 登録がエラーとなった場合の処理を実装
if (err) {
alert(JSON.stringify(err));
return;
}
// 登録が成功した場合の処理を実装(E-mailは未認証)
if (result) {
let cognitoUser = result.user;
console.log(cognitoUser);
// E-mailが再取得できない場合の処理を実装(基本的に発生しない)
if (!refEmail.current?.value) {
alert("missing email");
return;
}
// Contextから取得した画面間受け渡し用の関数にE-mailを渡す(E-mail認証画面で再度使用するため)
setEmail(refEmail.current.value);
// E-mail認証画面へ遷移する
router.push("/confirm");
} else {
alert("missing result");
return;
}
}
);
};
return (
<>
<h1>This is Signup page</h1>
<div>
E-mail:
<input type="text" ref={refEmail} />
Password:
<input type="text" ref={refPassword} />
<button onClick={submitHandler}>SignUp</button>
</div>
</>
);
};
export default Signup;
pages/confirm.tsx
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { useContext, useRef } from "react";
import { ConfirmContext } from "../components/ConfirmContext";
const Confirm = () => {
// 画面間での受け渡し用のContextからE-mailを取り出す
const { email } = useContext(ConfirmContext);
// 画面内で使用するrefを定義
const refCode = useRef<HTMLInputElement>(null);
// Cognito User Poolへの接続情報を設定(実際には環境変数などから取得した方が良い)
const poolData = {
UserPoolId: "<UserPoolId>",
ClientId: "<ClientId>",
};
// Cognito User Poolへ接続
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
// Confirmボタン押下時処理
const submitHandler = (e: { preventDefault: () => void }) => {
// 認証コード未入力エラー時のハンドリングを実装する
if (!refCode.current?.value) {
console.log("Code is Empty");
return;
}
// E-mail認証の対象とするCognito User Poolとユーザを設定する
let userData = {
Username: email,
Pool: userPool,
};
let cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
// E-mail認証処理を呼び出し
cognitoUser.confirmRegistration(
refCode.current.value,
true,
function (err, result) {
// E-mail認証がエラーとなった場合の処理を実装
if (err) {
alert(JSON.stringify(err));
return;
}
// E-mail認証が成功した場合の処理を実装
console.log(result);
}
);
};
return (
<>
<h1>This is Confirm page</h1>
<div>
Code:
<input type="text" ref={refCode} />
<button onClick={submitHandler}>Confirm</button>
</div>
</>
);
};
export default Confirm;
pages/login.tsx
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { useRef } from "react";
const Login = () => {
// 画面内で使用するrefを定義
const refEmail = useRef<HTMLInputElement>(null);
const refPassword = useRef<HTMLInputElement>(null);
// Cognito User Poolへの接続情報を設定(実際には環境変数などから取得した方が良い)
const poolData = {
UserPoolId: "<UserPoolId>",
ClientId: "<ClientId>",
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
// Loginボタン押下時の処理を実装
const submitHandler = (e: { preventDefault: () => void }) => {
// E-mail未入力エラー時のハンドリングを実装する
if (!refEmail.current?.value) {
console.log("E-mail is Empty");
return;
}
// パスワード未入力エラー時のハンドリングを実装する
if (!refPassword.current?.value) {
console.log("Password is Empty");
return;
}
// ログイン認証の対象とするCognito User Poolとユーザを設定する
let userData = {
Username: refEmail.current.value,
Pool: userPool,
};
let cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
// 認証用に渡すusernameとpasswordを設定する
let authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
{
Username: refEmail.current.value,
Password: refPassword.current.value,
}
);
// ログイン認証を実行する
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
// ログイン成功時の処理を実装する
console.log("Login Success");
console.log(result);
console.log(
"Access Token(jwtToken)=" + result.getAccessToken().getJwtToken()
);
console.log("ID Token(jwtToken)=" + result.getIdToken().getJwtToken());
console.log("Refresh Token=" + result.getRefreshToken().getToken());
console.log(
"username(congitoの一意ID)=" +
result.getAccessToken().decodePayload().username
);
},
onFailure: function (err) {
// ログイン失敗時の処理を実装する
alert(JSON.stringify(err));
},
});
};
return (
<>
<h1>This is Login page</h1>
<div>
E-mail:
<input type="text" ref={refEmail} />
Password:
<input type="text" ref={refPassword} />
<button onClick={submitHandler}>Login</button>
</div>
</>
);
};
export default Login;
※<UserPoolId>
、<ClientId>
には Cognito で事前に取得した User Pool ID とアプリケーションの ClientID を設定して下さい。
備考
上記は Client Secret を使用しない例で、クライアント処理で記載しています。
Client Secret を使する場合は、aws-sdk
のほうを利用する必要があります。
参考:Congito SDK 公式
パスワード忘れ処理など、その他の処理も基本的に同じ流れで実装できます。
Discussion