RemixにAuth0認証を組み込んでみた
はじめに
こんにちは、CX事業本部MAD事業部の森茂です。
Webアプリケーションをつくるにあたって多くの場合に必要なってくるのが認証の機能です。とはいえ認証の仕組みを1から整えるのはとても大変です。今回はIDaaSとして多く利用されているAuth0を利用した認証の仕組みをRemixに組み込んでみました。
Remixの公式リポジトリにAuth0を利用したテンプレートがマージされたのでこちらのソースも参考にしてください
Remixのインストール
RemixアプリケーションはCLIからテンプレートを作成できるのでCLIを利用してプロジェクトを作成します。今回は基本的な構成のRemix App Server
で構築します。
$ npx create-remix@latest
Remixのインストールや初期設定については下記記事も参照ください。
Auth0の組み込み
RemixでAuth0を利用した認証を行いたい場合、SSRを利用するためAuth0から配布されている公式ライブラリはそのまま利用できず若干手間がかかっていましたが、Remixの開発チームメンバーでもある@sergiodxa 氏のremix-auth
ライブラリを利用することで手軽に環境が用意できます。また今回はAuth0用のStrategyテンプレートのライブラリも一緒にインストールします。
sergiodxa/remix-auth: Simple Authentication for Remix
$ npm install remix-auth remix-auth-auth0
今回は実際に表示を行うURLは/
とlogin
のみ。認証前にはログインページにしかアクセスできず、認証後にトップページへアクセスができるというページ構成を想定して作成していきます。
URL | ページ種別 | 備考 |
---|---|---|
/ | トップページ | 認証あり |
/login | ログインページ | 認証なし |
/auth0 | Auth0へのリダイレクト用 | POSTのみ受け取る |
/callback | Auth0から認証後にリダイレクトされるURL | なし |
/logout | ログアウト用 | POSTのみ受け取る |
Auth0の設定を反映
事前にAuth0のアカウントやアプリケーションの用意が必要となります。アプリケーションの設定画面より下記情報をあらかじめ取得し環境変数として諒するために.env
ファイルとしてプロジェクトルートへ用意しておいてください。
新規にAuth0の環境を用意される場合はは下記記事が参考になると思います。
AUTH0_RETURN_TO_URL=http://localhost:3000 AUTH0_CALLBACK_URL=http://localhost:3000/callback AUTH0_CLIENT_ID=clientID AUTH0_CLIENT_SECRET=clientSecret AUTH0_DOMAIN=******.jp.auth0.com AUTH0_LOGOUT_URL=https://******.jp.auth0.com/v2/logout SECRETS=foobar
あわせてアプリケーション内から環境変数を利用するためにdotenv
ライブラリのインストールと環境変数読み込み用のファイルを用意します。
$ npm install dotenv
import { config } from 'dotenv'; config(); export const AUTH0_RETURN_TO_URL = process.env.AUTH0_RETURN_TO_URL!; export const AUTH0_CALLBACK_URL = process.env.AUTH0_CALLBACK_URL!; export const AUTH0_CLIENT_ID = process.env.AUTH0_CLIENT_ID!; export const AUTH0_CLIENT_SECRET = process.env.AUTH0_CLIENT_SECRET!; export const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN!; export const AUTH0_LOGOUT_URL = process.env.AUTH0_LOGOUT_URL!; export const SECRETS = process.env.SECRETS!;
Auth0認証用ロジック
Auth0への認証を行うためのサーバー側のロジックを用意します。Remixでは*.server.ts
とすることでサーバーサイドでのみ動作するスクリプトを明示的に記載することができます。
import { Authenticator } from 'remix-auth'; import { Auth0Strategy } from 'remix-auth-auth0'; import { createCookieSessionStorage } from 'remix'; import { AUTH0_CALLBACK_URL, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_DOMAIN, SECRETS, } from '~/constants'; export type User = { email: string; }; // ユーザーのEmailアドレスを受け取り返すだけのサンプル関数 export async function login(email: string): Promise<User> { return { email }; } const sessionStorage = createCookieSessionStorage({ cookie: { name: '_remix_session', sameSite: 'lax', path: '/', httpOnly: true, secrets: [SECRETS], secure: process.env.NODE_ENV === 'production', }, }); export const authenticator = new Authenticator(sessionStorage); const auth0Strategy = new Auth0Strategy( { callbackURL: AUTH0_CALLBACK_URL, clientID: AUTH0_CLIENT_ID, clientSecret: AUTH0_CLIENT_SECRET, domain: AUTH0_DOMAIN, }, async ({ profile }) => { // profileにAuth0のプロフィール情報が返ってきます console.log(profile); // // 返ってきた情報を利用してDBへ書き込むなどの処理 // 加工するなどの処理を入れる // // 今回はユーザーのEmailアドレスを返す関数を返すのみ return await login(profile.emails[0].value); }, ); authenticator.use(auth0Strategy); export const { getSession, commitSession, destroySession } = sessionStorage;
ログイン画面の作成
ログイン、ログアウトの一連の動作を行うためのファイルを作成します。
- app/routes/index.tsx
- app/routes/login.tsx
- app/routes/logout.tsx
- app/routes/auth0.tsx
- app/routes/callback.tsx
未ログインのユーザーが/
にアクセスすると/login
にリダイレクトされ、ログインボタンを謳歌すると/auth0
にPOSTを送り、そのままAuth0のログイン認証画面へリダイレクト。認証処理後に/callback
に戻り、問題なく認証されていれば/
にリダイレクトされるという流れになります。
ログアウト時はPOSTを/logout
を送ることでセッション情報がクリアされ/login
ページへ再びリダイレクトされます。
app/routes/index.tsx
import type { LoaderFunction } from 'remix'; import { authenticator, User } from '~/utils/auth.server'; import { useLoaderData } from 'remix'; export const loader: LoaderFunction = async ({ request }) => { const user = await authenticator.isAuthenticated(request, { failureRedirect: '/login', }); return { user }; }; export default function Index() { const data = useLoaderData<{ user: User }>(); return ( <div> {data.user && ( <> <form action="logout" method="post"> <button>Logout</button> </form> <h1>{data.user.email}</h1> </> )} <h1>Welcome to Remix</h1> </div> ); }
app/routes/login.tsx
export default function Login() { return ( <form action="auth0" method="post"> <button>Login with Auth0</button> </form> ); }
app/routes/logout.tsx
import { ActionFunction, redirect } from 'remix'; import { destroySession, getSession } from '~/utils/auth.server'; import { AUTH0_CLIENT_ID, AUTH0_LOGOUT_URL, AUTH0_RETURN_TO_URL, } from '~/constants'; export let action: ActionFunction = async ({ request }) => { const session = await getSession(request.headers.get('Cookie')); const logoutURL = new URL(AUTH0_LOGOUT_URL); logoutURL.searchParams.set('client_id', AUTH0_CLIENT_ID); logoutURL.searchParams.set('returnTo', AUTH0_RETURN_TO_URL); return redirect(logoutURL.toString(), { headers: { 'Set-Cookie': await destroySession(session), }, }); };
app/routes/auth0.tsx
import type { ActionFunction, LoaderFunction } from 'remix'; import { redirect } from 'remix'; import { authenticator } from '~/utils/auth.server'; export const loader: LoaderFunction = () => redirect('/login'); export const action: ActionFunction = ({ request }) => { return authenticator.authenticate('auth0', request); };
app/routes/callback.tsx
import type { ActionFunction, LoaderFunction } from 'remix'; import { authenticator } from '~/utils/auth.server'; export const loader: LoaderFunction = ({ request }) => { return authenticator.authenticate('auth0', request, { successRedirect: '/', failureRedirect: '/login', }); };
ひととおりファイルができあがったところで開発サーバーを起動し、ブラウザで/login
へアクセスしてみましょう。
ログインボタンを押下するとAuth0のログインページへ飛び、認証後に/
へ戻ってくるはずです。認証がうまくいっている場合はページ内にログインユーザーのメールアドレスが表示されます。
なお、認証が必要なページで下記LoaderFunction
を利用することで認証情報の取得や、状態によるリダイレクト処理を行うことができます。
... export const loader: LoaderFunction = async ({ request }) => { const user = await authenticator.isAuthenticated(request, { failureRedirect: '/login', }); return { user }; }; ...
さいごに
RemixでSSRを利用したAuth0の認証機能をサクッと組み込んでみました。remix-auth
ライブラリを利用することで手間をかなり軽減することができました。Strategyテンプレートを利用することでOAuth2を利用した各種認証方式にも対応できるのでAuth0だけでなく他の認証サービスとの連携にも活躍しそうです。