Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What are the problem?

posted at

updated at

【第2回】「みんなのポートフォリオまとめサイト」を作ります~SPA認証で死闘編~

はじめに

この連載記事は、僕が「みんなのポートフォリオまとめサイト」を作る過程をゼロから発信しながらみなさんに見てもらいつつ、作っている途中からみなさんにアドバイスをいただいて、よりよいサービスにしていきたいというお話です。

あとは「サービスを作っていく過程って初学者の人にとっては結構興味ある内容だったりするのでは?(少なくとも自分は知りたかった!)」と思い、このようなスタイルで記事を書いています。

一週間に一度のペースで更新しますとか言っちゃいましたが、前回更新からなんと1ヶ月半も経ってしまった!
今後も完成までゆるく更新していく予定です。

前回までの記事

【第0回】「みんなのポートフォリオまとめサイト」を作ります~宣言編~
「とりあえずこれから作るからみんな見てて!」と宣言しただけの記事。

【第1回】「みんなのポートフォリオまとめサイト」を作ります~着手編~
仕様を決めたり、サービス名考えたり、デザインを作ったり。

関連記事

【第0回】「みんなのポートフォリオまとめサイト」を作ります~宣言編~
【第1回】「みんなのポートフォリオまとめサイト」を作ります~着手編~

これまでやったことと作業時間

※ 集計期間 〜10/6

やったこと 前回までの作業時間 ( h ) 今回の作業時間 ( h )
仕様決め(競合調査) 3
サービス名考案 2
手書きでWF作成 2
デザイン作成 8
DB設計 0 4
実装(環境構築) 0 4.5
実装 0 25
その他 2

作業時間計 ( h )

前回まで 今回 合計
17 33.5 50.5

1ヶ月半で33.5hという怠惰っぷり。何をしていたんだろう(前回も同じようなこと言ってる。)
Reactでアウトプットするのが初めてなんですが、Udemyの講座で軽めにインプットしたのが1ヶ月以上前だったので完全に忘れてしまって思い出すためにUdemyの動画を見たり、社内LTでの登壇が2回あってその準備をしていたりしてましたね(言い訳)。

参考:Reactの勉強に使ったUdemyの講座たち

React×Reduxの基礎はこちらで
フロントエンドエンジニアのための React ・ Redux アプリケーション開発入門

Hooksの基礎はこちらで
React Hooks 入門 - Hooksと Redux を組み合わせて最新のフロントエンド状態管理手法を習得しよう!

あ、あと会社でReactのチュートリアルをもらったのでそれを苦しみながら進めたりもしていましたね。React×Redux自体慣れてないところにTypeScriptとテストコードも組み込まれていて、途中から何が分からないのかが分からないくらいに難しかったので挫折してしましました。このアプリを作ってレベルアップしてから再挑戦する。

DB設計

だいたい似たようなアプリケーションは数回作ったことあるので、過去の自作アプリを参考にサクっと設計していきます。

ちゃんとやるんだったらER図を作ったりしながらやるのがいいんでしょうが、面倒臭がりなのでスプレッドシートでやっちゃいました。
(ER図を作れるサイト:dbdiagram.io

こんな感じ↓↓↓
スクリーンショット 2020-10-04 21.42.42.png

softdeleteってのはカラム名ではなくLaravelのソフトデリートという「論理削除」機能のことを指していて、削除メソッドを実行してもDBにレコードは残ったままで、代わりにdeleted_atカラムに削除時間が記録されます。deleted_atに値が入っているものは、データ一覧を取得した際も含まれないので便利です。

Laravel ソフトデリート

環境構築

Laravelに関しては必要最低限な分だけやり、フロントに関しても以前のプロジェクトのものをまるっと持ってくることで時間節約します。
個人的には、browser-syncだけ動いてくれればそれでいい。保存したら自動でブラウザを更新、それだけで十分です。

ちなみにLaravelのバージョンは v 7.26.1でインストール当時の最新版です。
(7系は記事がまだ少ないので、6系にしとけばよかったと思いました。。)

package.json
{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "@babel/preset-react": "^7.0.0",
        "axios": "^0.19",
        "bootstrap": "^4.0.0",
        "browser-sync": "^2.25.0",
        "browser-sync-webpack-plugin": "^2.0.1",
        "cross-env": "^7.0",
        "import-glob-loader": "^1.1.0",
        "jquery": "^3.2",
        "laravel-mix": "^5.0.1",
        "lodash": "^4.17.19",
        "popper.js": "^1.12",
        "react": "^16.2.0",
        "react-dom": "^16.2.0",
        "react-redux": "^7.2.1",
        "react-router-dom": "^5.2.0",
        "redux": "^4.0.5",
        "resolve-url-loader": "^3.1.0",
        "sass": "^1.15.2",
        "sass-loader": "^8.0.0",
        "styled-components": "^5.2.0"
    }
}
webpack.mix.js

const mix = require('laravel-mix');

mix.webpackConfig({
    module: {
        rules: [{
            test: /\.scss/,
            enforce: 'pre',
            loader: 'import-glob-loader'
        }]
    }
})
/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.react('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .sourceMaps()
    .browserSync({ 
        https: false, // httpsのサイトをproxyするならtrueをセット
        files: [ 
            './resources/**/*',
            './app/**/*',
            './config/**/*',
            './routes/**/*',
            './public/**/*'
        ],
        proxy: {
            target: 'http://127.0.0.1:8000'
        },
        open: true, //BrowserSync起動時にブラウザを開く
        reloadOnRestart: true //BrowserSync起動時にブラウザにリロード命令おくる
    });

フロントのnpmモジュールはnodeのバージョン依存によるエラーが頻繁に起こって超厄介なのですが、browser-syncも例外なくそれにあたります。
以前のプロジェクトで、
node: v 9.11.0
browser-sync: v 2.25.0
という組み合わせでうまくいったので、今回もbrowser-syncはv2.25.0を指定してインストール。ちなみに今回nodeはv 12.11.0ですが、これでうまくいきました。

あ、あとESLintとStyleLintは入れたいところだな。。こいつらも設定がバッティングしてしまってうまく動かすのが難しいんですよね。。

いざ実装

SPAってなんぞ?

SPAとは、Single Page Application(シングルページアプリケーション)の略称であり、単一のWebページでアプリケーションを構成する設計構造の名称です。

SPAの説明については別リンク参照しておきますが、ユーザー目線でいうと「動作がサクサクになって快適!」ってことで是非とも採用したいと考えました。

デメリットの一つとして実装コストがかかるとのことですが、気合いで乗り切りましょう(無策

(参考)SPAについて
SPA(Single Page Application)ってなに?
SPA(Single Page Application)の基本

認証系をSPAで書き換える

以前の記事を書いてから今回までの間、ほとんどの時間をこの「認証系のSPAへの書き換え」に費やしていました。
Laravelはコマンド一つで認証系が構築できてしまうのですが、SPAをやろうとするといろいろとLaravel側の設定を書き換えなければいけなくて、これがまあ難しかった。。。

セッション認証とトークン認証

認証系を理解するうえで、この違いを認識するところからのスタートでした。(正直いまだにあんまり理解できてない)
フォームから値を送信してユーザー登録やログインを行うのはセッション認証です。
一方トークン認証は、ログイン情報をサーバー側に保持しないという点でセッション認証と違います。

(参考)セッション認証とトークン認証についてはこちら
Cookie(Session)での認証と Token での認証の違いについて
JWT・Cookieそれぞれの認証方式のメリデメ比較

Laravelの「Sanctum」という認証システム

そして、LaravelにはSPA認証をお手軽に実装できる「Sanctum」というライブラリが存在することを知ったので、こいつを使うことにしたのですが、ここで1つ大きな勘違いをしていました。

(参考)
Laravel 7.x Laravel Sanctum

何となくいろんなところで「SPAでwebアプリを作る場合、バックエンドをAPIとして使う」という情報を見聞きしていたので、「SPAならAPIかー」という浅い理解をしていました。

で、「SPA API 認証」というキーワードでぐぐると、「SPAではトークン認証をしましょう」という記事がヒットします。当然僕は、「そうか!SPAはトークン認証なんだ!」と思い、先ほどのSanctumを使ったトークン認証を実装していました。

↓これ
スクリーンショット 2020-10-06 6.12.06.png

トークン認証に書き換えるの、めっちゃ苦労しました。。。
(たぶん10時間くらいはかかりました。HTTPリクエストヘッダーの「Authorization」にBearerトークンをセットしなきゃいけないとか、まじで ?????? でした)

そしてもがき苦しみながらトークン認証に書き換え終わったころ、見てはいけないものを見てしまいました。

SanctumでSPAを認証するのにAPIトークンを使ってはいけません

先ほどのSanctumのAPIトークン認証のページ

スクリーンショット 2020-10-06 6.12.06.png

皆さん自身のファーストパーティSPAを認証するためにAPIトークンを決して利用してはいけません。代わりに、Sanctumの組み込みSPA認証を使用してください。

、、、トークン認証ダメ!ゼッタイ!ってめっちゃ書いてあるやんけ!!!

スクリーンショット 2020-10-06 6.24.29.png

うん、APIトークン認証のちょっと下にSPA認証の項ががっつり書いてあった。。

よくよく調べてみると、「Laravel×SanctumでSPA認証」みたいな記事をみると、ちゃんとこのSPA認証の内容が載ってる。。

(参考)Laravel×Sanctum によるSPA認証の記事

一番わかりやすかった記事
Laravel 7.x Sanctumの使い方!実例

SanctumじゃないしVueの記事だけど、SPAでアプリを作るチュートリアルでLaravel側の設定などはかなり参考になる
Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (4) 認証API

どうやら、SPAのように「フロントとバックの分離」をするときはトークン認証をするのが一般的?なようですが、SanctumではLaravelに組み込まれているセッション認証を使うようです。セッション認証だとCSRF保護が使えたりするので、トークン認証よりセキュリティ面で安心なようです(説明間違ってたらごめんなさい。。)

書き換えたトークン認証の処理を戻す

というわけで、頑張って書き換えたトークン認証の処理は必要ないということが分かりました。
セッション認証ならそんなに難しくないので、さくっとトークン認証から書き換えて一件落着。

まとめ

今回の個人開発、技術的な側面でいうと「Reactのアウトプット」がメインだったはずなのに、今のところほぼLaravelしか触ってない。サーバーサイドは使い慣れたLaravelで学習コストを抑えよう〜なんて思ってたけど、とんでもなかった。SPAの認証って難しい。

かなりもがき苦しみましたが、認証系をSPAに書き換える過程でHTTP通信についてもかなり勉強して理解が深まった気がします。自分でHTTPリクエストのヘッダーを書き換えたり追加したりなんてしたことなかった。

みんなのポートフォリオまとめサイト、いつになったら完成できるのか。
最初は9月いっぱいで作るとかふざけたこと言ってたような気がします。10月ですら無理そうなので、11月いっぱいを目標に変更しよう。

亀の歩みですが、認証系が一通り終わったのでSPA実装の最初の「0→1」フェーズは超えたと思ってます。
ここからは一気に加速して、、、いく、、、、はず

おわりに

Qiitaは不定期更新でやっておりますが、Twitter ( @kiwatchi1991 ) では日々感じたことや細かい試行錯誤の様子などを発信しております。

また、ブログを毎日更新していて(今は50日を超えたあたり)、数日に一度個人開発に関する記事を書いています。

38日目 個人開発記⑤-実装開始-
39日目 個人開発記⑥-思い込み-
44日目 個人開発記⑦-結局Laravel-

各論はそっちで細かく書いてたりもするので、よかったらこちらもぜひのぞいてみてください。

それではまた次回!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?