1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」16日目の記事は、バックエンド開発の大きな壁の一つ、「認証」についてです。

APIを作れるようになったとて、バックエンドにおいての最大の壁が待っています。

それは...認証です!

ログイン機能などは作ろうと思いつつ挫折し、また作ろうと思い立っては挫折し、を繰り返していたので、今回は?回目の正直で、GoにおけるJWT認証に挑戦してみたいと思います。

この記事では、JWTの基本の「き」から、ログインAPIでJWTを発行し、そのJWTを使って特定のエンドポイントを保護する「認証ミドルウェア」を実装するまでを、解説していきます。

  • 自作のAPIにログイン機能を追加したいと考えている方
  • JWTの仕組みと、それをサーバーサイドでどう扱うのかを具体的に知りたい方
  • Goのnet/httpにおけるミドルウェアの考え方を学びたい方

この記事で、JWT認証の基本的なフローを理解し、GoでJWTの発行と検証を行うAPIを自力で実装できるように、頑張りましょう!

JWT (JSON Web Token) とは?

JWTは、認証情報をJSON形式で表現し、改ざん不可能な電子署名を付与したオープン標準(RFC 7519)です。

JWTの構造

JWTは、.で区切られた3つのパートから構成されます。

xxxxx.yyyyy.zzzzz

  1. ヘッダー (Header): トークンの種類(JWT)や、署名に使うアルゴリズム(HS256など)といったメタ情報を含むJSONをBase64Urlエンコードしたもの
  2. ペイロード (Payload): ユーザーIDや有効期限など、伝えたい情報(これをクレームと呼びます)を含むJSONをBase64Urlエンコードしたもの
  3. 署名 (Signature): ヘッダーとペイロードを、指定した秘密鍵(Secret Key)で署名したもので、この署名があるおかげで、サーバーは「このトークンは確かに自分が発行したもので、途中で改ざんされていない」ことを検証できる

JWT認証の全体フロー

JWTを使った認証は、一般的に以下の流れで進みます。

  1. ログイン: ユーザーがID/パスワードをサーバーに送信します。
  2. トークン発行: サーバーはID/パスワードを検証し、正しければユーザー情報を含むJWTを生成して、レスポンスとしてクライアントに返します。
  3. トークン保存: クライアント(ブラウザ)は、受け取ったJWTをlocalStorageなどに保存します。
  4. 認証付きリクエスト: 保護されたAPIにアクセスする際、クライアントはHTTPリクエストのAuthorizationヘッダーにBearer <JWT>の形式でトークンを付与します。
  5. トークン検証: サーバーは、リクエストを受け取るとAuthorizationヘッダーからJWTを抽出し、署名と有効期限を検証します。
  6. アクセス許可: 検証に成功すれば、リクエストされたAPIの処理を実行し、結果を返します。失敗すれば401 Unauthorizedエラーを返します。

【実践】GoでJWT認証を実装する

それでは、このフローをGoで実装していきましょう。

STEP1: 準備

go mod initした状態で始めていきます。

まず、GoでJWTを扱うためのデファクトスタンダードなライブラリgolang-jwt/jwtをインストールします。

go get github.com/golang-jwt/jwt/v5

次に、プロジェクトの雛形となるmain.goファイルを作成します。

package main

import (
	"encoding/json"
	"log"
	"net/http"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// JWTの署名に使う秘密鍵
var jwtSecretKey = []byte("my_secret_key")

// ダミーのユーザー情報
var users = map[string]string{
	"mamenz": "password123",
}

// JWTのペイロード(クレーム)を定義する構造体
type Claims struct {
	Username string `json:"username"`
	jwt.RegisteredClaims
}

func main() {
	// ルーティング
	http.HandleFunc("/login", loginHandler)
	// TODO: /api/private を認証ミドルウェアで保護する

	log.Println("サーバーがポート番号8080で起動しています...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

STEP2: ログインハンドラの実装

/loginエンドポイントで、ユーザー名とパスワードを受け取り、検証が成功したらJWTを発行するハンドラを実装します。

// main.go に追加

// loginHandler はユーザー認証を行い、成功すればJWTを返す
func loginHandler(w http.ResponseWriter, r *http.Request) {
	// リクエストボディからユーザー名とパスワードを取得
	var creds struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// ユーザー名とパスワードを検証
	expectedPassword, ok := users[creds.Username]
	if !ok || expectedPassword != creds.Password {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}

	// JWTの有効期限を設定 (ここでは5分)
	expirationTime := time.Now().Add(5 * time.Minute)

	// ペイロード(クレーム)を作成
	claims := &Claims{
		Username: creds.Username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expirationTime),
		},
	}

	// ヘッダーとペイロードからトークンを生成
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// トークンに秘密鍵で署名
	tokenString, err := token.SignedString(jwtSecretKey)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// レスポンスとしてトークンを返す
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"token": tokenString,
	})
}

STEP3: 認証ミドルウェアの実装

次に、認証ミドルウェアを実装します。ミドルウェアは、リクエストを最終的なハンドラに渡す前に、共通の処理(今回はJWTの検証)を挟み込むためのものです。

// main.go に追加

// authMiddleware はリクエストヘッダーのJWTを検証するミドルウェア
func authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Authorizationヘッダーからトークンを取得
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			http.Error(w, "Authorizationヘッダーが必要です", http.StatusUnauthorized)
			return
		}

		// "Bearer "プレフィックスを削除
		tokenString := authHeader[len("Bearer "):]

		// トークンをパースして検証
		claims := &Claims{}
		token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
			return jwtSecretKey, nil
		})

		if err != nil {
			if err == jwt.ErrSignatureInvalid {
				w.WriteHeader(http.StatusUnauthorized)
				return
			}
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		if !token.Valid {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		// トークンが有効であれば、次のハンドラに処理を渡す
		next.ServeHTTP(w, r)
	})
}

STEP 4: 保護されたエンドポイントの作成とミドルウェアの適用

最後に、認証が必要なエンドポイント/api/privateと、それをauthMiddlewareで保護するルーティングを追加します。

// main.go に追加

// privateHandler は認証が必要なエンドポイント
func privateHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Welcome to the private area!"))
}

// main関数を修正
func main() {
	// publicなエンドポイント
	http.HandleFunc("/login", loginHandler)

	// privateなエンドポイントをミドルウェアで保護
	privateMux := http.NewServeMux()
	privateMux.HandleFunc("/api/private", privateHandler)
	
	// /api/private へのリクエストは、まずauthMiddlewareで処理される
	http.Handle("/api/private", authMiddleware(privateMux))

	log.Println("Server starting on port 8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

これで完成です!

STEP5: 動作確認

curlコマンドを使って動作を確認してみましょう。

まずはサーバーを起動します。

go run main.go

サーバーはそのままに別のターミナルを起動して、最初は、保護されたエリアに直接アクセスしてみます。

curl http://localhost:8080/api/private
# -> Authorization header is required

トークンがないため、401エラー(ミドルウェアからのメッセージ)が返ってきます。

次に、ログインしてJWTを取得します。

curl -X POST -H "Content-Type: application/json" -d '{"username":"mamenz", "password":"password123"}' http://localhost:8080/login
# -> {"token":"<YOUR_TOKEN>"}

レスポンスとしてJWTが返ってきました。

取得したJWTを使って、再度プライベートエリアにアクセスします。

<YOUR_TOKEN>の部分を、上記で取得したトークンに置き換えてください。

curl -H "Authorization: Bearer <YOUR_TOKEN>" http://localhost:8080/api/private
# -> Welcome to the private area!

ミドルウェアがトークンを検証し、リクエストをprivateHandlerに通したので正しく認証されました。

おわりに

この記事では、JWTとは何か、GoバックエンドにおけるJWTの実装方法について解説しました。

JWT認証の実装、最初は「うわ、難しそう...」と感じた方もいるかもしれません。

ここでのポイントは、「ログインハンドラで行う処理」と「ミドルウェアで行う処理」の区別をしっかり行うことだと感じました。
処理をどのように行うか手順が混乱すると、認証も苦手感が出てきます。

これから応用パターンを実装していくにあたって、基本理解のためのいい勉強になりました。

最後までお読みいただき、ありがとうございました!

この「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」では、GoJavaを使い、APIの作り方、データベースとの接続、テストやDockerといった気になったバックエンド技術の基本を振り返った学びを共有しています。

ぜひ他の記事もチェックして、筆者がこのひとりアドカレを完遂することができるか、確認してみてください(^^)

学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025

それでは、明日の「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」の記事もお楽しみに!

参考文献

jwt.io/ja/introduction#what-is-json-web-token-structure

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?