【Next.js】Auth0で認証が必要な画面の作り方
Next.jsでAuth0を実装してアプリを作るとログイン状態でしか見せたくないページもあるでしょう。たとえば、ユーザーのプロフィールを設定する画面に直接URLを入力したときとか。ログアウト状態だと遷移せずにログイン画面にリダイレクトしたいと考えますよね。
実際、zennでもログアウト状態でhttps://zenn.dev/settings/profile
にアクセスしてプロフィール編集画面を開こうとするとログイン画面が表示されるようになっています。
なので、今回はログアウト状態で認証が必要な画面に遷移したときに、ログイン画面にリダイレクトする方法について解説します。
Next.jsでAuth0を設定する方法についてはこちらでまとめています。
クライアント側で認証が必要な場合
nextjs-auth0のgithubのサンプルに載っている/components/with-auth.jsxを使えばログアウト状態で遷移すればログイン画面にリダイレクトされます。以下のソースではログアウト状態で遷移するとログイン画面にリダイレクトする画面を作っています。
pages/protected-page.jsx(ログイン状態でしか見せない画面)
import React from 'react';
import Layout from '../components/layout';
import { useFetchUser } from '../lib/user';
import withAuth from '../components/with-auth';
export function ProtectedPage() {
const { user, loading } = useFetchUser();
return (
<Layout user={user} loading={loading}>
<h1>Protected Page</h1>
{loading && <p>Loading profile...</p>}
{!loading && user && (
<>
<p>Profile:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
</>
)}
</Layout>
);
}
//高次コンポーネントを使ってログイン画面にリダイレクト
export default withAuth(ProtectedPage);
/components/with-auth.jsx(ログアウト状態だとリダイレクトするコンポーネント)
import React, { Component } from 'react';
import auth0 from '../lib/auth0';
import { fetchUser } from '../lib/user';
import createLoginUrl from '../lib/url-helper';
import RedirectToLogin from '../components/login-redirect';
export default function withAuth(InnerComponent) {
return class Authenticated extends Component {
static async getInitialProps(ctx) {
//api/meにアクセスしてユーザー情報を取得
if (!ctx.req) {
const user = await fetchUser();
return {
user
};
}
//セッションのユーザー情報を取得
const session = await auth0.getSession(ctx.req);
if (!session || !session.user) {
ctx.res.writeHead(302, {
Location: createLoginUrl(ctx.req.url)
});
ctx.res.end();
return;
}
//ユーザー情報をコンポーネントの引数(this.props)に渡す
return { user: session.user };
}
constructor(props) {
super(props);
}
render() {
//ユーザー情報が存在しなければログイン画面にリダイレクト
if (!this.props.user) {
return <RedirectToLogin />;
}
return <div>{<InnerComponent {...this.props} user={this.props.user} />}</div>;
}
};
}
protected-page.jsxでは、高次コンポーネントを使って認証機能を追加しています。
高次コンポーネントとは、コンポーネントを受け取り、コンポーネントを生成する関数のことです。Protected-page
のコンポーネントをwithAuth
に渡すことで認証機能を追加しています。
with-auth.jsxではサーバーサイドレンダリングした際に、以下のどちらかの処理によってユーザー情報を取得しています。
-
/api/me
にアクセスした際に取得できるユーザー情報 - セッションに格納されているユーザー情報
どちらもログアウト状態ではユーザー情報は取得できないようになっており、そのユーザー情報をコンポーネントの引数(this.props
)に渡しています。
そして、渡された引数にユーザー情報が存在しなければrender
メソッドでログイン画面にリダイレクトしているのです。RedirectToLogin
タグの部分が該当箇所になります。
/components/login-redirect.jsx
import Router from 'next/router';
import React, { Component } from 'react';
import Layout from '../components/layout';
import createLoginUrl from '../lib/url-helper';
export default class RedirectToLogin extends Component {
componentDidMount() {
//マウントされたときにログイン画面をロード
window.location.assign(createLoginUrl(Router.pathname));
}
render() {
return (
<Layout>
<div>Signing you in...</div>
</Layout>
);
}
}
login-redirect.jsx
ではマウントされたときに、window.location.assign
でログイン画面を表示しているだけです。
これにより、認証が必要な状態で遷移するとログイン画面にリダイレクトされるわけです。
サーバーサイドでログイン画面で認証が必要な場合
認証はページだけでなく、Web APIでも必要な場合があるでしょう。そんな時に使えるのがrequireAuthentication
です。
以下のソースでは、認証が必要なWeb APIであるcallを作成しています。http://localhost:3000/api/call
でアクセス可能です。
page/api/call.jsx
import auth0 from '../../lib/auth0';
export default auth0.requireAuthentication(async (req, res) => {
res.json({
call: '0000-000-000'
});
});
ログイン状態でアクセスすればcallの値を取得できますが、ログアウト状態でアクセスすると「認証できません」のエラーが返されています。
ログイン状態
ログアウト状態
そのため、以下のようにクリックハンドラーでcallAPIを呼び出すとログイン状態だとWeb APIから渡された値を取得できますが、ログアウト状態だとundefined
になります。
page/index.jsx
import React from 'react';
import Layout from '../components/layout';
import { useFetchUser } from '../lib/user';
export default function Home(props) {
const { user, loading } = useFetchUser();
//callAPIから値を取得
const fetchCall = async () => {
const response = await fetch("http://localhost:3000/api/call");
const data = await response.json();
alert("call:" + data.call)
}
return (
<Layout user={user} loading={loading}>
<h1>Next.js and Auth0 Example</h1>
{/* ロード中 */}
{loading && <p>Loading login info...</p>}
{/* ログアウト状態 */}
{!loading && !user && (
<>
{/* 取得できない */}
<button onClick={fetchCall}>ログアウト状態</button>
</>
)}
{/* ログイン状態 */}
{user && (
<>
{/* 取得できる */}
<button onClick={fetchCall}>ログイン状態</button>
</>
)}
</Layout>
);
}
ログイン状態
ログアウト状態
まとめ
- クライアント側で認証が必要な場合、
with-auth
を使えばログイン画面にリダイレクトされる - サーバーサイドで認証が必要な時は
requireAuthentication
を使えば認証されていない場合、エラーとなる
Discussion