AWS AmplifyでCognitoユーザーとデータを紐付ける方法
はじめに
AWS Amplify はサーバレスな Web アプリケーションのバックエンドを簡単に作成出来るサービスです。コマンド一つでユーザー認証や API を作成出来る。とくに AWS Amplify は API を GraphQL で作成でき、モダンな web 開発を簡単に実現出来ます。
今回は、GraphQL で Tweet のモデルを作成し、Cognito ユーザーのアクセス制御について説明する。
Tweet のデータモデル
この記事では以下のようなデータモデルを扱います。
ID は Tweet の ID ですべての Tweet 対して一意に設定されます。owner はこの Tweet を作成したユーザーです。content は Tweet 内容です。
今回、Tweet は以下のような権限を持ちます。
- owner 以外は他人の Tweet を Read 出来る。
- owner は Create, Read, Delete 出来る。
これを GraphQL のスキーマに書き起こすと以下の様になる。
type Tweet
@model
@auth(rules: [
{allow: owner, provider: userPools, operations: [create, delete]},
{allow: public, provider: iam, operations: [read]}
]) {
id: ID!
owner: String
content: String!
}
owner はこの Tweet の所有者を表すカラムで、provider に UserPools が設定されているとこのデータを作成したユーザーの ID が保存される。owner を required に設定はしない。owner のカラム名を変更したくなったら以下のように ownerField を設定する。
{allow: owner, ownerField: "userid", provider: userPools, operations: [create, update, delete]},
また、public へのアクセスは IAM ロールを用いる。
React のフロントエンド
Next.js でフロントエンドを作成する。
create-next-app
でプロジェクトを作成する。以下をプロジェクトにインストールする。
# npm i aws-amplify @aws-amplify/ui-react
_app.tsx を以下のように編集する。
import type { AppProps } from "next/app";
import { Amplify, AuthModeStrategyType } from "aws-amplify";
import "@aws-amplify/ui-react/styles.css";
import { AmplifyProvider } from "@aws-amplify/ui-react";
import awsconfig from "@/aws-exports";
Amplify.configure({
...awsconfig,
DataStore: {
authModeStrategyType: AuthModeStrategyType.MULTI_AUTH,
},
});
export default function App({ Component, pageProps }: AppProps) {
return (
<AmplifyProvider>
<Component {...pageProps} />
</AmplifyProvider>
);
}
Cognito 認証と IAM 認証が共存させるためには、authModeStrategyType: AuthModeStrategyType.MULTI_AUTH,
を設定する。
index.tsx を以下のように編集。
import Head from "next/head";
import { DataStore } from "@aws-amplify/datastore";
import { Auth } from "aws-amplify";
import { Tweet } from "@/models";
import {
Authenticator,
Button,
Card,
Collection,
Flex,
TextAreaField,
} from "@aws-amplify/ui-react";
import { useEffect, useState } from "react";
export default function Home() {
const [tweets, setTweets] = useState<Tweet[]>([]);
useEffect(() => {
fetchTweets();
}, []);
const [tweet, setTweet] = useState("");
const fetchTweets = async () => {
const fetchedTweets = await DataStore.query(Tweet);
setTweets(fetchedTweets);
};
const createTweet = async (content: string) => {
const newTweet = await DataStore.save(new Tweet({ content }));
console.log(newTweet);
setTweets((tweets) => [...tweets, newTweet]);
};
const deleteTweet = async (tweet: Tweet) => {
await DataStore.delete(tweet).then((t) => {
setTweets((tweets) => {
return tweets.filter((t) => t.id !== tweet.id);
});
});
};
const signOut = async () => {
await Auth.signOut();
};
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<Authenticator>
<Flex
as="form"
direction="column"
onSubmit={(e) => {
e.preventDefault();
createTweet(tweet);
}}
>
<TextAreaField
onChange={(e) => setTweet(e.target.value)}
label="create tweet"
labelHidden={true}
isRequired={true}
/>
<Button type="submit">Tweet</Button>
</Flex>
<Button onClick={fetchTweets}>List</Button>
<Button onClick={signOut}>SignOut</Button>
<Collection items={tweets} type="list">
{(tweet) => (
<Card key={tweet.id}>
{tweet.content}
<Button onClick={() => deleteTweet(tweet)}>Delete</Button>
</Card>
)}
</Collection>
</Authenticator>
</main>
</>
);
}
これで Tweet が出来る。
Tweet を全件取得する方法。
const fetchedTweets = await DataStore.query(Tweet);
Tweet を削除する方法。
await DataStore.delete(tweet);
Tweet を作成する方法。
const newTweet = await DataStore.save(new Tweet({ content }));
owner に required を設定するとこれで owner を設定しないのでエラーになる。
おわりに
Amplify で Tweet の CRUD を作成してみた。これでなんでも作れるようになるにはもう少し修行が必要かな。
Discussion