1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SolidJSAdvent Calendar 2024

Day 2

SolidStartと認証(auth0)

Last updated at Posted at 2024-12-01

この記事は、SolidJSアドベントカレンダー 2024 2 日目の記事です。

はじめに

SolidStartの認証機能の実装の記事がなかったので書いてみました。

ライブラリ

mediakit を使います。

@auth/solid-start でもいいのですが、@auth/solid-startはsolidstartがv0.2の時のAPIを参照してexampleなどが書かれてるので今回はsolidstartのv1に対応してるmediakitを使っていきます。

インストール

auth.jsを内部で使ってるので@auth/coreも必要です。

pnpm add @solid-mediakit/auth @auth/core

実装

.env
AUTH0_ISSUER=<issuer>
AUTH0_CLIENT_ID=<client_id>
AUTH0_CLIENT_SECRET=<client_secret>
VITE_AUTH_PATH=/api/auth
AUTH_URL=http://localhost:3000
AUTH_SECRET=<secret> # https://authjs.dev/guides/environment-variables#auth-secret を見て生成してください

AUTH0_ISSUER, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET はauth0の以下の箇所から取得してください。

スクリーンショット 2024-11-30 225547.png

AUTH0 から始まる3つは私が命名しただけなので、自由に変えてもらって大丈夫です。他3つはライブラリから参照されてるので名前を変更しないでください。

続いてauth.jsを使うときのお約束のauthOptionを作成します。

src/lib/auth/option.ts
import Auth0 from "@auth/core/providers/auth0";
import type { SolidAuthConfig } from "@solid-mediakit/auth";

import crypto from "node:crypto";

if (process.env.NODE_ENV === "development") {
  // authjs内部でcryptoを使っているが、なぜか「cryptoがない」と怒られるためglobalに代入する
  global.crypto = crypto as never;
}

declare module "@auth/core/jwt" {
  interface JWT {
    accessToken: string;
  }
}
declare module "@auth/core/types" {
  interface Session {
    accessToken: string;
  }
}

const auth0 = Auth0({
  issuer: process.env.AUTH0_ISSUER,
  clientId: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
});

export const authOption: SolidAuthConfig = {
  basePath: process.env.VITE_AUTH_PATH,
  providers: [auth0],
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        token.accessToken = account?.access_token ?? "";
      }
      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken;
      return session;
    },
  },
};

auth.jsをssrで使うときのお約束のapi routesを作成します。

src/routes/api/auth/[...route].ts
import { authOption } from "~/lib/auth/option";
import { SolidAuth, type SolidAuthConfig } from "@solid-mediakit/auth";

const handlers = SolidAuth(authOption);
export const GET = handlers.GET;
export const POST = handlers.POST;

セッションを読み込みを一元管理するためにSesssionProviderをつけます。

src/app.tsx
import { MetaProvider, Title } from "@solidjs/meta";
import { Route, Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";
import { SessionProvider } from "@solid-mediakit/auth/client";

export default function App() {
  return (
      <Router
        preload={false}
        root={(props) => (
          <MetaProvider>
            <Title>SolidStart - Basic</Title>
            <Suspense>
              <SessionProvider>{props.children}</SessionProvider>
            </Suspense>
          </MetaProvider>
        )}
      >
        <FileRoutes />
      </Router>
  );
}

設定完了したのでログインします。

src/routes/index.tsx
import { useAuth } from "@solid-mediakit/auth/client";
import { onMount } from "solid-js";

export default function Page() {
  const auth = useAuth();
  onMount(() => {
    auth.signIn("auth0", { callbackUrl: window.location.origin }); // http://localhost:3000にリダイレクトさせる
  });
  return <div>Loading...</div>;
}

これでとりあえず使えます。

実際にはToken Rotationの処理も実装することにはなると思うのですが、auth.jsを使った他のフレームワークでのサンプルが探せばあると思うのでそちらを参照してください。

tips

ログアウト

ログイン同様に実装できます。

import { useAuth } from "@solid-mediakit/auth/client";
import { onMount } from "solid-js";

export default function Page() {
  const auth = useAuth();
  onMount(() => {
    auth.signIn("auth0", { callbackUrl: window.location.origin });
  });
  return <div>Loading...</div>;
}

ページ保護

mediakitにも protected$があるのですが、propsがセッションだけに固定されるので自作したほうがいいと思います。

補足:Next.jsのlayout.tsxで認証チェックすると情報漏洩するかもが気になったので確認してみましたが、認証通るまで子コンポーネントがレンダリングされず子コンポーネントのfetchが走らないため大丈夫でした。(fetch結果を必要としないdomはclientに渡ってたので注意は必要そう)

src/components/protectedRoute.tsx
import { getSession } from "@solid-mediakit/auth/client";
import { createAsync, query, redirect } from "@solidjs/router";
import { type Component, Show } from "solid-js";
import { getRequestEvent } from "solid-js/web";

const getUser = query(async () => {
  "use server";
  const event = getRequestEvent();
  const session = await getSession(event);
  if (!session) {
    throw redirect("/");
  }
  return session;
}, "media-user");

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export function protectedRoute<T extends Record<string, any>>(
  Comp: Component<T>
) {
  return (props: T) => {
    const session = createAsync(() => getUser());
    return <Show when={session()}>{(_) => <Comp {...props} />}</Show>;
  };
}

import type { RouteSectionProps } from "@solidjs/router";
import { protectedRoute } from "~/components/protectedRoute";

export default protectedRoute<RouteSectionProps>(function PrivateLayout(props) {
  return (
    <div>
      <div>private</div>
      {props.children}
    </div>
  );
});

セッションのpreload

サーバーサイドでセッションを取得できるようmiddlewareを作成します。

これがないとクライアント側での取得になります。

solidstartではmiddlewareを読み込ませるには設定をする必要がありますが、それについては公式ドキュメントを参照してください。

src/middlewares.ts
import { authMiddleware } from "@solid-mediakit/auth";
import { createMiddleware } from "@solidjs/start/middleware";
import { authOption } from "./lib/auth/option";
import { redirect } from "@solidjs/router";

export default createMiddleware({
  onRequest: [
    authMiddleware(true, authOption),
  ],
});

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?