はじめに
「学生ひよこ界隈が送る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
- ヘッダー (Header): トークンの種類(JWT)や、署名に使うアルゴリズム(HS256など)といったメタ情報を含むJSONをBase64Urlエンコードしたもの
- ペイロード (Payload): ユーザーIDや有効期限など、伝えたい情報(これをクレームと呼びます)を含むJSONをBase64Urlエンコードしたもの
- 署名 (Signature): ヘッダーとペイロードを、指定した秘密鍵(Secret Key)で署名したもので、この署名があるおかげで、サーバーは「このトークンは確かに自分が発行したもので、途中で改ざんされていない」ことを検証できる
JWT認証の全体フロー
JWTを使った認証は、一般的に以下の流れで進みます。
- ログイン: ユーザーがID/パスワードをサーバーに送信します。
- トークン発行: サーバーはID/パスワードを検証し、正しければユーザー情報を含むJWTを生成して、レスポンスとしてクライアントに返します。
-
トークン保存: クライアント(ブラウザ)は、受け取ったJWTを
localStorageなどに保存します。 -
認証付きリクエスト: 保護されたAPIにアクセスする際、クライアントはHTTPリクエストの
AuthorizationヘッダーにBearer <JWT>の形式でトークンを付与します。 -
トークン検証: サーバーは、リクエストを受け取ると
AuthorizationヘッダーからJWTを抽出し、署名と有効期限を検証します。 -
アクセス許可: 検証に成功すれば、リクエストされた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」では、GoやJavaを使い、APIの作り方、データベースとの接続、テストやDockerといった気になったバックエンド技術の基本を振り返った学びを共有しています。
ぜひ他の記事もチェックして、筆者がこのひとりアドカレを完遂することができるか、確認してみてください(^^)
学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025
それでは、明日の「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」の記事もお楽しみに!
参考文献
jwt.io/ja/introduction#what-is-json-web-token-structure