📑

Next.js+TSでNextAuth.jsを使ってcognito認証を行う

2022/05/02に公開

webアプリを作っていて、ログイン部分の実装のためにcognito認証を使ったので、その方法について書きます。
今回はAWS側で予め用意されているhosted UIを使う場合の記事となります。

Cognito側の設定

Amazon Cognito コンソールに移動して、ログイン後ユーザープールを作成します。
アプリクライアントの設定が必要になるので、設定しておきます。

ユーザープールが作成できたらアプリの統合 > アプリクライアントの設定からログイン時のコールバックURLに http://localhost:3000/api/auth/callback/cognito と入力します。

あとは必要なOAuthスコープを選択して、適当なドメイン名を追加すれば設定完了です。

NextAuth.jsのインストール

まずNextAuth.js[1]をインストールします。

yarn add next-auth

必要な環境変数の確認

https://next-auth.js.org/providers/cognito を読んで必要なenvの値をセット&&型補完がきくようにglobals.d.tsを追加します。(この手順は好みなのでなくても良いです)

globals.d.ts
declare namespace NodeJS {
  // 環境変数名の定義
  interface ProcessEnv {
    readonly COGNITO_CLIENT_ID: string;
    readonly COGNITO_CLIENT_SECRET: string;
    readonly COGNITO_ISSUER: string;
  }
}

COGNITO_CLIENT_ID、COGNITO_CLIENT_SECRET、COGNITO_ISSUERに関してはそれぞれawsコンソールから確認して、.env.localに追加しておきます。

APIルートを追加

上記でコールバックURLに指定したhttp://localhost:3000/api/auth/callback/cognito に当たるルートの処理を書きます。

apiフォルダ配下にauthを作成し、[...nextauth].tsという名前のファイルを作成します。

pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';

export default NextAuth({
  providers: [
    CognitoProvider({
      clientId: process.env.COGNITO_CLIENT_ID,
      clientSecret: process.env.COGNITO_CLIENT_SECRET,
      issuer: process.env.COGNITO_ISSUER,
    }),
  ],
});

これでhttp://localhost:3000/api/auth/callback/cognito にアクセスされた場合、上記の処理が走り、cognito認証ができるようになります。

クライアント側からcognitoログインできるようにする

最後に、クライアント側からcognito認証するための動線を追加します。
まずはSessionProviderでコンポーネントをラップします。

_app.tsx
import { SessionProvider } from 'next-auth/react';
import type { AppProps } from 'next/app';
import '../styles/globals.css';

function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

export default MyApp;

これでNextAuth.js側で用意されているhooks類などが呼び出せるようになります。
接続できることを確認するために、https://next-auth.js.org/getting-started/example#frontend---add-react-hook にかかれてあるとおりの処理をindex.tsxに追加します。

index.tsx
import type { NextPage } from 'next';
import { signIn, signOut, useSession } from 'next-auth/react';

const Home: NextPage = () => {
  const { data: session } = useSession();
  if (session && session.user) {
    return (
      <>
        Signed in as {session.user.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  );
};

export default Home;

起動するとSigninというボタンが見えます。

クリックすると以下の画面になります。※この画面はカスタム可能です(後述)

これを更にクリックするとhosted UIが現れます。

カスタマイズ

[...nextauth].ts内にpagesを追加し、サインインページなどを別途指定することもできます。

[...nextauth].ts
import NextAuth from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';

export default NextAuth({
  providers: [
    CognitoProvider({
      clientId: process.env.COGNITO_CLIENT_ID,
      clientSecret: process.env.COGNITO_CLIENT_SECRET,
      issuer: process.env.COGNITO_ISSUER,
    }),
  ],
+  pages: {
+    signIn: '/auth/signin',
+    signOut: '/auth/signout',
+    error: '/auth/error', // Error code passed in query string as ?error=
+    verifyRequest: '/auth/verify-request', // (used for check email message)
+    newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
+  }
});

https://next-auth.js.org/configuration/pages

この上で、例えば/auth/signinページを作成します。

pages/auth/signin.tsx
import { GetServerSideProps, NextPage } from 'next';
import { OAuthProviderType } from 'next-auth/providers';
import {
  ClientSafeProvider,
  getProviders,
  LiteralUnion,
  signIn,
} from 'next-auth/react';

type Props = {
  providers: Record<
    LiteralUnion<OAuthProviderType, string>,
    ClientSafeProvider
  >;
};

const SignIn: NextPage<Props> = ({ providers }) => {
  return (
    <>
      {Object.values(providers).map((provider) => (
        <div key={provider.name}>
          <button onClick={() => signIn(provider.id)}>
            Sign in with {provider.name}
          </button>
        </div>
      ))}
    </>
  );
};

export const getServerSideProps: GetServerSideProps = async (context) => {
  const providers = await getProviders();
  return {
    props: { providers },
  };
};

export default SignIn;

再び起動してSigninボタンをクリックすると上記の画面が表示されます。

ただし、hosted UIに関してはcssで多少のカスタムはできるもののフルカスタマイズとなるとやはり最初に書いたとおりSDKを使わないと厳しいので、あくまでhosted UIにうつるまでの動線部分のカスタムとなります。

デプロイする際に行うこと

デプロイする際に、https://next-auth.js.org/getting-started/example#deploying-to-production に記述がある通り、NEXTAUTH_URLという環境変数が必要となるので、追加してからデプロイする必要があります。

また、デプロイしたら、コールバックURLをhttp://localhost:3000 からアプリのURLに変更することも忘れないようにします。

以上です。


参考

https://next-auth.js.org/

脚注
  1. ちなみに、今回NextAuth.jsを使った理由は今後認証方法がcognito以外になったとしても書き換えが少ないかなと思ったからです。amplifyを使うほうが慣れているのですが、他の認証方法を知らないと思い挑戦してみました。 ↩︎

Discussion