RedwoodJSに入門してみた(第4回: dbAuthによる認証)
この記事について
この記事は、全5回の第4回です。
RedwoodJSに入門してみた(第1回: アプリ作成〜モデル作成)
RedwoodJSに入門してみた(第2回: CRUD作成 API編)
RedwoodJSに入門してみた(第3回: CRUD作成 WEB編)
RedwoodJSに入門してみた(第4回: dbAuthによる認証)
RedwoodJSに入門してみた(第5回: 実際に触ってみて感じたこと)
今回はRedwoodアプリに認証を実装する
Redwoodでは組み込みで認証のライブラリが用意されている。
Auth0やFirebaseなど、サードパーティの認証も利用できるが、この記事では組み込みの認証システムであるdbAuth
を使用する。
認証のセットアップ
yarn rw setup auth dbAuth
を実行する
? Overwrite existing /api/src/lib/auth.[jt]s? › (y/N)
上書きして問題ないのでy
を入力
? Enable WebAuthn support (TouchID/FaceID)? See https://redwoodjs.com/docs/auth/dbAuth#webAuthn › (y/N)
こちらは今回必要ないのでN
を入力
package.jsonに@redwoodjs/auth
も追加されている
web/src/App.tsx
に<AuthProvider>
が追加される
+ import { AuthProvider } from '@redwoodjs/auth'
+
import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
...
const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
+ <AuthProvider type="dbAuth">
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
+ </AuthProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)
User モデルを追加
schema.prisma
にUserモデルを追加して、yarn rw prisma migrate dev
を実行する
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
hashedPassword String
salt String
resetToken String?
resetTokenExpiresAt DateTime?
}
UUIDを使用する場合
schema.prisma
は下記のようになっている想定
model User {
id String @id @default(uuid())
name String?
email String @unique
hashedPassword String
salt String
resetToken String?
resetTokenExpiresAt DateTime?
}
setup
コマンドで生成されるapi/src/lib/auth.ts
では、どうやらDBのUserの定義を参照していないらしく、UUIDやCUIDを使用している場合も下記のような関数が生成されている。
export const getCurrentUser = async (session: Decoded) => {
if (!session || typeof session.id !== "number") {
throw new Error("Invalid session");
}
return await db.user.findUnique({
where: { id: session.id },
select: { id: true },
});
};
このままだとログイン時にエラーが出てしまうので、下記のように修正する必要がある。
- if (!session || typeof session.id !== 'number') {
+ if (!session || typeof session.id !== 'string') {
暗号化キー
.env
にSESSION_SECRET
という変数が追加されている。この変数がユーザーログイン時のクッキーの暗号化キーになる。デプロイ先を変更する際など、yarn rw g secret
を実行すると新しい値を生成することができる。
ログイン・会員登録・パスワード忘れの画面追加
yarn rw g dbAuth
を実行すると、ログイン・サインアップ・パスワード忘れのページを追加できる
? Enable WebAuthn support (TouchID/FaceID) on LoginPage? See https://redwoodjs.com/docs/auth/dbAuth#webAuthn › (y/N)
またしても聞かれるが、今回は不要なのでN
を入力。
下記の4つのPage
とそれぞれのRoute
が追加される。
web/src/pages/LoginPage/LoginPage.tsx
web/src/pages/SignupPage/SignupPage.tsx
web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx
web/src/pages/ResetPasswordPage/ResetPasswordPage.tsx
web/src/Routes.tsx
const Routes = () => {
return (
<Router>
+ <Route path="/login" page={LoginPage} name="login" />
+ <Route path="/signup" page={SignupPage} name="signup" />
+ <Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />
+ <Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />
...
</Router>
)
}
非常に便利。
Web側での認証
useAuth()
useAuth()
というhooksから下記のように様々な認証情報にアクセスできる
import { useAuth } from "@redwoodjs/auth";
export const MyComponent = () => {
const { currentUser, isAuthenticated, logIn, logOut } = useAuth();
return (
<ul>
<li>The current user is: {currentUser}</li>
<li>Is the user logged in? {isAuthenticated}</li>
<li>
Click to{" "}
<button type="button" onClick={logIn}>
login
</button>
</li>
<li>
Click to{" "}
<button type="button" onClick={logOut}>
logout
</button>
</li>
</ul>
);
};
ログイン・ログアウトの実行、現在のログイン状態や、ログインしているユーザー情報の取得などができる。
Route
<Set>
コンポーネントにprivate
を指定することで認証されていないユーザーのアクセスを制限できる。
認証されていないユーザーがアクセスした場合のリダイレクト先はunauthenticated
で指定できる。hasRole
を指定することでユーザーのロールを制限することもできる。
ちなみに、<Private>
コンポーネントを使うことで<Set private>
と同様に扱える。基本的には<Private>
を使ったほうが良さそう。
const Routes = () => {
return (
<Router>
...
{/* PrivateとSetを分けているパターン */}
<Private unauthenticated="login" hasRole={["author", "editor"]}>
<Set wrap={FooLayout}>
<Route path="/foo" name="foo" page={FooPage} />
</Set>
</Private>
{/* Privateにまとめて書くことができる */}
<Private unauthenticated="login" wrap={FooLayout}>
<Route path="/foo" name="foo" page={FooPage} />
</Private>
<Route notfound page={NotFoundPage} />
</Router>
);
};
API側での認証
API側ではcontext.currentUser
とすることでユーザーの情報にアクセスできる。
getCurrentUser()
createGraphQLHandlerにgetCurrentUserを渡すことで、デコードされた情報をユーザーオブジェクトにマッピングできる。
クライアントに返す情報は慎重に定義したいので、setup
コマンドで作成されたgetCurrentUser
はid
しか返さないようになっている。
id
以外も取得したい場合は下記のように追加する必要がある。
export const getCurrentUser = async (session: Decoded) => {
if (!session || typeof session.id !== 'number') {
throw new Error('Invalid session')
}
return await db.user.findUnique({
where: { id: session.id },
select: {
id: true,
+ email: true,
},
})
}
GraphQL Directive
api/src/lib/auth.ts
が上書きされることにより、すべてtrue
を返していたrequireAuth()
が認証情報をチェックするようになる。
デフォルトで作成されたQueryやMutationは@requireAuth
で定義されている。認証の設定をする前はauthorizedがtrueになっているため実行できるが、認証の設定後はログインしていないとfalseが返るようになり、実行が許可されない。
認証をスキップしたい場合は@skipAuth
に書き換えることで、認証していないユーザーも実行できるようになる。
次回
次回はRedwoodJSを使ってみた所感をまとめる。
Discussion