🐦

AWS AmplifyでCognitoユーザーとデータを紐付ける方法

2023/04/04に公開

はじめに

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