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.tsxweb/src/pages/SignupPage/SignupPage.tsxweb/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsxweb/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