Golang EchoフレームワークでJWT認証を実装する方法

# 初心者向けガイド：Golang EchoフレームワークでJWT認証を実装する方法（Mac環境）

ウェブアプリケーションの開発において、ユーザー認証は重要な要素の一つです。
特に、セキュアな認証システムを構築するために、JSON Web Tokens（JWT）は広く利用されています。
本記事では、Golang（Go）言語とEchoフレームワークを使用して、JWT認証を実装する方法を初心者にもわかりやすく解説します。
Mac環境での検証を前提としています。

## 目次

1. [プロジェクトのセットアップ](#プロジェクトのセットアップ)
   - [1. Goのインストール](#1-goのインストール)
   - [2. プロジェクトディレクトリの作成](#2-プロジェクトディレクトリの作成)
   - [3. `go.mod` の初期化](#3-gomod-の初期化)
   - [4. `.env` ファイルの作成と秘密鍵の生成](#4-env-ファイルの作成と秘密鍵の生成)
2. [主要なファイルの解説](#主要なファイルの解説)
   - [go.mod](#gomod)
   - [.env](#env)
   - [server.go](#servergo)
3. [サーバーの起動とテスト](#サーバーの起動とテスト)
   - [サーバーの起動](#サーバーの起動)
   - [テスト手順](#テスト手順)
4. [まとめ](#まとめ)

---

## プロジェクトのセットアップ

まず、Golangと必要なパッケージをインストールします。
以下の手順に従ってプロジェクトをセットアップしましょう。

### 1. Goのインストール

Goがインストールされていない場合は、[公式サイト](https://golang.org/dl/)からインストールしてください。
ターミナルで以下のコマンドを実行し、インストールが完了していることを確認します。

```bash
go version
```

**出力例:**

```
go version go1.21.4 darwin/amd64
```

### 2. プロジェクトディレクトリの作成

ターミナルを開き、以下のコマンドでプロジェクトディレクトリを作成します。

```bash
mkdir echo_jwt_auth
cd echo_jwt_auth
```

### 3. `go.mod` の初期化

Goモジュールを初期化し、必要な依存関係を追加します。

```bash
go mod init echo_jwt_auth
go get github.com/labstack/echo/v4
go get github.com/golang-jwt/jwt
go get github.com/joho/godotenv
```

### 4. `.env` ファイルの作成と秘密鍵の生成

JWTの署名に使用する秘密鍵を生成します。Mac環境では、`openssl`コマンドを使用して安全な秘密鍵を生成できます。

ターミナルで以下のコマンドを実行してください。

```bash
openssl rand -base64 32
```

このコマンドは、32バイトのランダムなデータをBase64エンコードした文字列を生成します。生成された文字列をコピーして、後ほど `.env` ファイルに貼り付けます。

**例:**

```
F2JS6V1YoAM9mxTcvaX0RjLHntQCyq7mTzftufvYmfU=
```

`.env` ファイルをプロジェクトのルートディレクトリに作成し、以下の内容を追加します。

```env
JWT_SECRET=F2JS6V1YoAM9mxTcvaX0RjLHntQCyq7mTzftufvYmfU=
```

> **注意**: `.env`ファイルには機密情報を含めるため、バージョン管理システム（例：Git）には含めないように `.gitignore` に追加することをおすすめします。

## 主要なファイルの解説

プロジェクトのディレクトリ構造は以下の通りです：

```
echo_jwt_auth/
├── go.mod
├── server.go
├── go.sum
└── .env
```

### go.mod

Goモジュールの設定ファイルで、プロジェクト名や依存関係を定義します。

```go
module echo_jwt_auth

go 1.21.4

require (
    github.com/golang-jwt/jwt v3.2.2+incompatible
    github.com/labstack/echo/v4 v4.12.0
    github.com/joho/godotenv v1.5.1
)
```

- **module echo_jwt_auth**: モジュール名を `echo_jwt_auth` に設定。
- **go 1.21.4**: 使用するGoのバージョン。
- **require**: 必要なパッケージを指定。

### .env

環境変数を定義するファイルです。ここではJWTの秘密鍵を設定しています。

```env
JWT_SECRET=F2JS6V1YoAM9mxTcvaX0RjLHntQCyq7mTzftufvYmfU=
```

> **注意**: `.env`ファイルには機密情報を含めるため、バージョン管理システム（例：Git）には含めないように `.gitignore` に追加することをおすすめします。

### server.go

メインのサーバーコードです。Echoフレームワークを使用してJWT認証を実装します。コードを分割し、メイン関数を簡素化することで、可読性を高めています。

```go
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/golang-jwt/jwt"
    "github.com/joho/godotenv"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

// Userはユーザー情報を表す構造体です
type User struct {
    Name     string `json:"name" xml:"name" form:"name" query:"name"`
    Email    string `json:"email" xml:"email" form:"email" query:"email"`
    Password string `json:"password" xml:"password" form:"password" query:"password"`
}

// JwtCustomClaimsはJWTのカスタムクレームを定義します
type JwtCustomClaims struct {
    Email string `json:"email"`
    Name  string `json:"name"`
    jwt.StandardClaims
}

func main() {
    // 環境変数の初期化
    if err := initEnv(); err != nil {
        log.Fatal(err)
    }

    // Echoインスタンスの作成
    e := echo.New()

    // ミドルウェアの設定
    configureMiddleware(e)

    // ルートの定義
    defineRoutes(e)

    // サーバーの起動
    if err := e.Start(":1323"); err != nil {
        log.Fatal("サーバーの起動に失敗しました:", err)
    }
}

// initEnvは.envファイルから環境変数を読み込みます
func initEnv() error {
    err := godotenv.Load()
    if err != nil {
        return fmt.Errorf(".envファイルの読み込みに失敗しました: %v", err)
    }

    if os.Getenv("JWT_SECRET") == "" {
        return fmt.Errorf("JWT_SECRETが.envファイルに設定されていません")
    }

    return nil
}

// configureMiddlewareはEchoに必要なミドルウェアを設定します
func configureMiddleware(e *echo.Echo) {
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
}

// defineRoutesはアプリケーションのルートとハンドラーを設定します
func defineRoutes(e *echo.Echo) {
    // 環境変数からJWTシークレットを取得
    jwtSecret := []byte(os.Getenv("JWT_SECRET"))

    // JWTミドルウェアの設定
    jwtMiddleware := middleware.JWTWithConfig(middleware.JWTConfig{
        SigningKey: jwtSecret,
        Claims:     &JwtCustomClaims{},
    })

    // パブリックルート: ログイン
    e.POST("/login", login)

    // 保護されたルート: Hello World
    e.GET("/", hello, jwtMiddleware)
}

// helloは保護されたハンドラーで、挨拶メッセージを返します
func hello(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
}

// loginはユーザー認証を行い、JWTトークンを生成して返します
func login(c echo.Context) error {
    // リクエストのJSONをUser構造体にバインド
    user := new(User)
    if err := c.Bind(user); err != nil {
        log.Println("リクエストのバインドエラー:", err)
        return c.JSON(http.StatusBadRequest, echo.Map{"message": "無効な入力です"})
    }

    log.Println("ログイン試行:", user.Email)

    // 簡易的な認証チェック（実際のアプリケーションではデータベースと連携）
    if user.Email != "user@example.com" || user.Password != "password" {
        log.Println("認証エラー:", user.Email)
        return c.JSON(http.StatusUnauthorized, echo.Map{"message": "メールアドレスまたはパスワードが無効です"})
    }

    // JWTクレームの設定
    claims := &JwtCustomClaims{
        Email: user.Email,
        Name:  user.Name,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: time.Now().Add(72 * time.Hour).Unix(),
        },
    }

    // クレームを持つトークンを生成
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // トークンを署名
    t, err := token.SignedString(jwtSecret)
    if err != nil {
        log.Println("トークンの署名エラー:", err)
        return c.JSON(http.StatusInternalServerError, echo.Map{"message": "トークンを生成できませんでした"})
    }

    // トークンを返す
    return c.JSON(http.StatusOK, echo.Map{
        "token": t,
    })
}
```

## サーバーの起動とテスト

### サーバーの起動

以下のコマンドでサーバーを起動します。

```bash
go run server.go
```

サーバーが正常に起動すると、以下のようなログが表示されます：

```
2024/04/27 12:00:00 Listening on :1323
```

### テスト手順

APIエンドポイントをテストするために、`curl`コマンドを使用します。以下の手順に従ってテストを行ってください。

#### 1. ログインしてJWTトークンを取得

まず、ログインエンドポイントにユーザー情報を送信して、JWTトークンを取得します。

```bash
curl -X POST http://localhost:1323/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password", "name": "Sample User"}'
```

**期待されるレスポンス**：

```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJuYW1lIjoiU2FtcGxlIFVzZXIiLCJleHAiOjE3MzA4NTU1MTZ9.1AWyT7129JbdoZ6wx0YGLAXJxY1Y4DxeYxYZ0D75HMI"
}
```

このトークンを後続のリクエストで使用します。

#### 2. 保護されたエンドポイントにアクセス

取得したJWTトークンを使用して、保護されたルートにアクセスします。

```bash
curl http://localhost:1323/ \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJuYW1lIjoiU2FtcGxlIFVzZXIiLCJleHAiOjE3MzA4NTU1MTZ9.1AWyT7129JbdoZ6wx0YGLAXJxY1Y4DxeYxYZ0D75HMI"
```

**期待されるレスポンス**：

```
Hello, World!
```

#### 3. 認証失敗時の挙動

不正なトークンやトークンを送信しない場合、アクセスは拒否されます。

**例**: トークンを送信しない場合

```bash
curl http://localhost:1323/
```

**期待されるレスポンス**：

```json
{
  "message": "Missing or malformed JWT"
}
```

## まとめ

本記事では、GolangとEchoフレームワークを使用して、JWT認証を実装する方法を解説しました。

### 学んだポイント

- **プロジェクトのセットアップ手順**:
  - Goモジュールの初期化と依存関係の追加方法。

- **JWTの秘密鍵の生成方法（Mac環境）**:
  - `openssl`コマンドを使用した安全な秘密鍵の生成手順。

- **Echoフレームワークを用いた基本的なサーバー構築**:
  - Echoインスタンスの初期化とミドルウェアの設定方法。

- **JWT認証の実装方法**:
  - ユーザー認証のロジックとJWTトークンの生成・署名方法。

- **`curl`コマンドを使用した認証フローのテスト手順**:
  - ログインエンドポイントへのリクエストとトークンを使用した保護されたエンドポイントへのアクセス方法。


## 参考リンク

- [Golang公式ドキュメント](https://golang.org/doc/)
- [Echoフレームワーク公式サイト](https://echo.labstack.com/)
- [JSON Web Tokens (JWT) 公式サイト](https://jwt.io/)
- [godotenv GitHubリポジトリ](https://github.com/joho/godotenv)
- [OpenSSL公式サイト](https://www.openssl.org/)

これらのリソースを活用して、さらに深い理解と高度な機能の実装に挑戦してみてください！

---
### 会社紹介

**株式会社 Mosaica**  
最先端テクノロジーで社会課題を解決し、持続可能な未来を創造する IT カンパニー。
AI ソリューション、クラウド統合、DX 推進、経営コンサルティングなど包括的なサービスでビジネス変革を支援しています。  

詳しくは [公式サイト](https://mosaica.co.jp/) までお気軽にご相談ください。
公式サイト： https://mosaica.co.jp/