🎧

Bearer認証についてまとめてみた。

2024/05/22に公開

Bearer認証とは

HTTP認証スキームの一つで、ユーザーの身元を確認する手段として利用されます。
この認証スキームではクライアントサイドは所定の形式で
「Bearer」トークンをサーバーに送信します。

Bearerトークンは、基本的には認証情報を持つ長い文字列で
通常はサーバーサイドで生成される。

クライアントが初めてログインしたときに、
または新しいトークンを要求したときに、サーバーからクライアントに「Bearerトークン」
を送信されてその後のリクエストではクライアントがトークンを使用して自身を認証します。

バックエンドAPIとやりとりしてBearer認証を実装する

1 依存関係のインストール

npm install express body-parser jsonwebtoken

2 簡易APIの作成
nodeでAPIをつくりそことのやり取りでBearer認証を使うようにしてみる

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const app = express();
const port = 3000;
const secretKey = 'your_secret_key';

app.use(bodyParser.json());

let users = [{ username: 'user1', password: 'password1' }];

app.post('/login', (req, res) => {
    const { username, password } = req.body;
    const user = users.find(u => u.username === username && u.password === password);

    if (user) {
        const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
        res.json({ token });
    } else {
        res.status(401).send('無効な資格情報です');
    }
});

const authenticateToken = (req, res, next) => {
    const token = req.headers['authorization'];
    if (!token) return res.sendStatus(403);

    jwt.verify(token.split(' ')[1], secretKey, (err, user) => {
        if (err) return res.sendStatus(403);
        req.user = user;
        next();
    });
};

app.get('/protected', authenticateToken, (req, res) => {
    res.send('保護されたルート');
});

app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

APIと接続する部分に関しては
async
awaitで非同期処理

async
*関数が必ず非同期処理であることを示します。
*asyncが付いた関数は、必ずPromiseを返します。

Promiseとは
主な3つの状態を手に入れる

Pending(保留中): 非同期処理がまだ完了していない、または開始していない状態。
Fulfilled(成功): 非同期処理が成功して、結果が利用可能な状態。
Rejected(失敗): 非同期処理が何らかの理由で失敗した状態。

await
*asnyc関数の中でのみ使用できます。
*Promiseの完了を待つ つまり上の三つと判定が入ったら次の行に進む
*awitは、Promiseが成功した場合にその結果を返します。失敗した場合はエラーをスローします。

ログインコンポーネントの作成

<template>
  <div>
    <h2>Login</h2>
    <form @submit.prevent="login">
      <input v-model="username" placeholder="Username" />
      <input v-model="password" type="password" placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
import axios from "axios";

export default {
  data() {
    return {
      username: "",
      password: "",
    };
  },
  methods: {
    async login() {
      try {
        const response = await axios.post("http://localhost:3000/login", {
          username: this.username,
          password: this.password,
        });
        localStorage.setItem("token", response.data.token);
        this.$router.push("/protected");
      } catch (error) {
        console.error("Login failed:", error);
      }
    },
  },
};
</script>

vuexの概念を事前に入れておくといいと思う。

<form @submit.prevent="login">
<input v-model="username" placeholder="Username" />
<input v-model="password" type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>

ボタンクリックがあった時にdispatchされる。

入力された情報

  methods: {
    async login() {
      try {
        const response = await axios.post("http://localhost:3000/login", {
          username: this.username,
          password: this.password,
        });

入力された情報がhttp://localhost:3000/loginに渡される

server.js

let users = [{ username: "user1", password: "password1" }];
app.post("/login", (req, res) => {
  const { username, password } = req.body;
  const user = users.find(
    (u) => u.username === username && u.password === password
  );

入力されたものが合っているかチェックする場所になります

if (user) {
    const token = jwt.sign({ username }, secretKey, { expiresIn: "1h" });
    res.json({ token });
  } else {
    res.status(401).send("Invalid credentials");
  }

もしユーザーがあればトークンを発行して
それ以外ではエラーを返す

const authenticateToken = (req, res, next) => {
  // Authorization ヘッダーからトークンを抽出します
  const token = req.headers["authorization"];
  // トークンが存在しない場合は、403 Forbidden 応答を送信します
  if (!token) return res.sendStatus(403);
  // 秘密鍵を使用してトークンを検証します
  jwt.verify(token.split(" ")[1], secretKey, (err, user) => {
    // 検証中にエラーが発生した場合は、403 Forbidden 応答を送信します
    if (err) return res.sendStatus(403);
    // ユーザー情報を req オブジェクトに添付します
    req.user = user;
    // next() を呼び出して、次のミドルウェアまたはルート ハンドラーに制御を渡します。
    next();

生成したtokenが有効なものかどうか確認します。
有効なものであれば

import { createRouter, createWebHistory } from "vue-router";
import Login from "@/components/Login.vue";
import Protected from "@/components/Protected.vue";

const routes = [
  {
    path: "/login",
    name: "Login",
    component: Login,
  },
  {
    path: "/protected",
    name: "Protected",
    component: Protected,
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

ルーティング経由で返却

Bearer認証の流れ

1 クライアントがログイン時にサーバーはアクセストークンを発行
2 発行されたトークンをクライアントのブラウザ内のsessionStrageかlocalStrageに保存
3 ページアクセス時にAuthorizationヘッダにトークンセットし、サーバーにRequest送信
  (下記コード参照)
4 サーバーはトークンを確認して有効ならリクエストされたページをレスポンス認証に
  失敗すると、サーバーは401Unauthorizedを送信する
  (トークンの形式は、token68の形式で指定することを定められています)

*token68
token68は、1文字以上の半角英数字,(-ハイフン),(ドット)_(アンダーバー)
~(チルダ),+(プラス),/(スラッシュ)から構成された文字列

//SessionStrageからトークンを取得して変数に代入
const token = sessionStorage.getItem('access_token');

fetch("/api", {
    method: 'GET',
// トークンを送信
    headers: {
    Authorization: 'Bearer ${token}'
}
}

発行したトークンはクライアント側がlocalStrageか、sessionStrage,
サーバー側はDBにそれぞれ保存します。

まとめ

Bearer認証はトークンをサーバー側に送信するしくみのこと
認証の確認:HTTP認証のスキームの一つで、ユーザーの身元を確認する手段として使用されます。
クライアントは「Bearer」トークンをサーバーに送信し、
サーバーはトークンの有効性を検証する。
「Bearerトークン」は、一般にJWT形式が使われることが多く、ペイロードには、
ユーザー情報や、有効期限などが含まれる

バックエンドAPIとやりとりしてBearer認証を実装する

  • JWTの生成と検証には秘密鍵を使用している

* コードの流れ ログインコンポーネント
フォームの送信時に、クライアントからサーバーにログイン情報を送信して、
トークンを受け取ってローカルストレージに保存し保護されたルートにリダイレクトします。

=>ログインが失敗したときのエラーハンドリングも含まれているのが重要です。

  • コードの流れ サーバーサイド
    ユーザー情報の検証、トークンの生成、トークンの検証、保護されたルートの設定など
    手順が含まれています。

Discussion