🧬

AppSyncでAuth0+Apolloを使いAmplifyをシカトする

2022/01/26に公開

こんにちは。AppSyncを知っていますか?AWSが提供しているフルマネージドGraphQLサービスです。
推奨ではAppSyncを利用する際にはAmplifyを使ってくださいね、という雰囲気がありますが色々あってAmplifyが嫌いな人もいて、モノレポ化した時にホスティング時に中のCIがぶっ壊れて何もデプロイできなくなったり、そもそもCLIがまともに動かない時代を経験した人などはAmplifyを信頼しづらいところがあると思います。
あとは、ホスティングがCloudfrontになってしまうのでもう少しSLAの高いVercelやCloudflareにしたかったり。
現状バックエンドAppSyncで頑張ってスタートダッシュを切りたいけど、将来的にアプリケーションは全面的にECS/Fargateでコンテナ化したかったり等あると思います。
そういう時期にAmplifyでよくわからない挙動に引っかかって時間を大損するというのはどうしても避けたいことですね。なのでAmplifyをシカトします。

Auth0の設定

AWSにはCognitoという大変ややこしい認証・認可のフルマネージドサービスがありますが、ややこしすぎるので今回はAuth0社のサービスを利用し、OpenID Connect(OIDC)認証を使うことにします。
ただし認可には使いません。そこは各自で実装してください

Auth0でアプリケーションとAPIを作ります。API作成の際にはIdentityを都度適切な値に置き換えてください。

このとき設定したIdentifierをAuth0上のaudienceとして利用します。

今回はNextjs上での話をしますが使うのは@auth0/nextjs-auth0ではなく通常のReactjsプロジェクトで利用する@auth0/auth0-reactです。あとはVueでもだいたいストーリーは一緒です。適宜読み替えるなどしてください。

AWSでの設定

AppSyncのコンソールを開き、適当なAPIを作成ののち、左ペインの「設定」からデフォルトの認証モードを変更します。

設定するのはとりあえずOpenID Connect プロバイダードメイン (発行者 URL)のみにします。これでAWS側の設定は完了です。

実装

以下のパッケージをインストールしてください。

$ yarn add @auth0/auth0-react @apollo/client aws-appsync-auth-link aws-appsync-subscription-link

今回の実装ではオフライン機能はついていませんが、実際にオフライン機能が必要になる時期がよくわからないのでオミットしてもいい方向けです。

Nextjsでは_app.tsxにAuth0などの設定を置きます。今回は_app.tsxにAuth0の設定を、Apolloの実装は別コンポーネントに分けることにします。

import { useAuth0 } from "@auth0/auth0-react";
import { ApolloProvider, ApolloClient, InMemoryCache, ApolloLink, createHttpLink } from "@apollo/client";
import { AuthOptions, createAuthLink } from "aws-appsync-auth-link";
import { createSubscriptionHandshakeLink } from "aws-appsync-subscription-link";
import { useMemo } from "react";

const region = "ap-northeast-1";
const url = process.env.NEXT_PUBLIC_APPSYNC_URL;
const audience = process.env.NEXT_PUBLIC_AUTH0_AUDIENCE;

export const ApolloProviderWithAuth0 = ({ children }) => {
    const { getAccessTokenSilently } = useAuth0();

    const auth = useMemo<AuthOptions>(() => {
        return {
            type: "OPENID_CONNECT",
            jwtToken: async () => {
                return await getAccessTokenSilently({
                    audience
                });
            }
        };
    }, [getAccessTokenSilently]);

    const link = ApolloLink.from([
        // 型エラーが起きるけど特に問題はないので無理矢理通す
        createAuthLink({ url, region, auth }) as unknown as ApolloLink,
        createSubscriptionHandshakeLink({ url, region, auth }, createHttpLink({ uri: url }))
    ]);

    const client = new ApolloClient({
        link,
        cache: new InMemoryCache()
    });

    return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

これで各ページでApolloを使う用意ができました。ただしAuth0にログインしていないとAppSyncが認証をはじくので、ログインするロジックも作っておきましょう。
@auth0/auth0-reactの場合は

loginWithRedirect({
    redirectUri: window.location.origin,
    audience: process.env.NEXT_PUBLIC_AUTH0_AUDIENCE
});

でAuth0のログインページに飛びます。

あとは自由にやっていけます。よいGraphQLライフを!

Discussion