くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

SSRなNuxtで認証情報をセッションに保存する

SSRなNuxt、リロードするとstoreから認証情報が消えてしまい、
認証状態で初期レンダリングできないのつらいなとおもっていたら、
Nuxtのサンプルがよさげだったので、自分用に再整理してみた。

認証ルート - Nuxt.js

ざっくりの方針

  • 認証情報をStoreに保存するときに、sessionにもいれておく
  • sessionにいれるために、serverMiddlewareを使う
  • serverMiddlewareではexpressを立てて、sessionに詰める/削除だけする

準備する。設定する。

ライブラリのインストール

npm install --save express express-session body-parser whatwg-fetch

serverMiddlewareの@/api/index.ts

serverMiddlewareで呼ばれる処理を書く。
セッションを詰める/削除するだけのAPIを用意。

import express from "express";
const router = express.Router();
const app = express();
router.use((req, res, next) => {
  Object.setPrototypeOf(req, app.request);
  Object.setPrototypeOf(res, app.response);
  req.res = res;
  res.req = req;
  next();
});


// API: /api/login
router.post("/login", (req: any, res) => {
  if (!!req.body.authUser) {
    req.session.authUser = req.body.authUser; // セッションにつめる
    return res.json(req.body.authUser);       // レスポンスはそのまま返す
  }
  res.status(401).json({ message: "Bad credentials" });
});

// API: /api/logout
router.post("/logout", (req: any, res) => {
  delete req.session.authUser;                // セッションの削除
  res.json({ ok: true });
});

// Export the server middleware
export default {
  path: "/api",
  handler: router
};

store/index.ts

storeはこんな感じ。特別な点はactions

  1. nuxtServerInitでsessionに認証情報があれば、storeに入れる
  2. loginで認証情報をserverMiddleware経由でセッションに保存
  3. logoutで認証情報をserverMiddleware経由でセッションを削除
import { GetterTree, ActionContext, ActionTree, MutationTree } from "vuex";
import { Context } from "@nuxt/vue-app";
import Vue from "vue";
import axios from "axios";

// ****************************************************
// * Types / interface
// ****************************************************
export interface User {
  uid: string;
  name: string;
  profileImage: string;
}

export interface Actions<S, R> extends ActionTree<S, R> {
  nuxtServerInit(context: ActionContext<S, R>, appContext: any): void;
}

export interface RootState {
  authUser: User | null;
}

// ****************************************************
// * STATE
// ****************************************************
export const state = (): RootState => ({
  authUser: null
});

// ****************************************************
// * MUTATIONS
// ****************************************************
export const mutations: MutationTree<RootState> = {
  SET_USER: function(state: RootState, user: User | null) {
    state.authUser = user;
  },
};

// ****************************************************
// * ACTIONS
// ****************************************************
export const actions: Actions<RootState, RootState> = {
  async nuxtServerInit({ commit }, { req }) {
    // セッションがあれば、storeに詰める
    if (req.session && req.session.authUser) {
      commit("SET_USER", req.session.authUser);
    }
  },

  async login({ commit }, { user }) {
    commit("SET_USER", user);
    
    // serverMiddlewareを呼び出してセッションに保存
    await axios.post("/api/login", { authUser: user });
  },

  async logout({ commit }) {
    commit("SET_USER", null);

    // serverMiddlewareを呼び出してセッションを削除
    await axios.post("/api/logout");
  }
};

nuxtconfig.ts

serverMiddlewareが有効になるように、nuxtconfig.tsに設定を追加。

import NuxtConfiguration from "@nuxt/config";
import bodyParser from "body-parser";
import session from "express-session";

const config: NuxtConfiguration = {
  // ... 略
  serverMiddleware: [
    // body-parser middleware
    bodyParser.json(),
    // session middleware
    session({
      secret: "super-secret-key",
      resave: false,
      saveUninitialized: false,
      cookie: { maxAge: 60000 }
    }),
    // Api middleware
    "~/api"
  ],
}

つかう

使うときはよくある感じ。

  1. midlleware/authed.tsで未認証の場合はリダイレクトする処理を用意
  2. 認証が必要なページでmidlleware:"authed"を設定

midlleware/authed.ts

/**
 * 認証が必要なページで未認証の場合はリダイレクトする
 */
export default async function({ store, redirect }) {
  if (!store.state.authUser) redirect("index");
}

page/secret.vue

import { Component, Vue } from "nuxt-property-decorator";

@Component({ middleware: "authed" })
export default class SecretPage extends Vue {
  // ... 略
}

ほかにも

PWAやlocalstorateをつかったり、Cookieをつかったりいろいろあるっぽい。
ベターはPWAっぽい感じだけど、お手軽にできるのはserverMiddlewareっぽいので、
サンプルのをベースにしてみた♪

FirebaseAuthをカラメルと複雑になるするんので、どうするのがいいのかは悩む。。
まだまだ、試行錯誤中。。

以上!!

参考にしたサイト様