認可コードフローによるトークンの取得
このページでは、認可コードフロー(OpenID Connect の認証方式の一つ)を使用してアクセストークンを取得する方法を説明します。
前のステップでユーザーがマイナンバーカードで署名を実行し、取得した認可コードを使用して、署名結果を取得するためのアクセストークンを取得します。
シーケンス
実装例
以下のコード例では、前ステップで取得した認可コードを使用してアクセストークンを取得します。
注記
JWT 生成関数 (generateJWT
) はクライアントクレデンシャルフローと同一です。
実際の実装では、共通関数として切り出すことを推奨します。
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
)
// クライアント情報
const (
// デジタル庁から発行されたクライアント IDです。ご自身のクライアント IDで置き換えてください。
clientID = "<YOUR_CLIENT_ID>"
// トークンエンドポイント
tokenURL = "https://sb-auth-and-sign.go.jp/api/realms/main/protocol/openid-connect/token"
// リダイレクト URLに含まれるクエリパラメータ `code` の値を設定します。
authCode = "AbcdEfgHijkLMNoPqrStuVwXyZ0123456789"
// 認可リクエストで設定した `redirect_uri` を設定します。
redirectURI = "https://aas.p8n.jp"
// 認可リクエストで作成した `code_verifier` の値を設定します。
cv = "c91f60547f2b8e47890e3a493b56e24b1081e3ff8087636b9add7973e55ddd01"
// クライアント認証で与えるjwtに署名するためのjwk
jwkJSON = `<YOUR_SECRET_JWK>`
)
// client_assertion を生成する関数
func generateJWT(clientID, tokenURL string, key jwk.Key) (string, error) {
tok, err := jwt.NewBuilder().
// iss: デジタル認証アプリサービスがクライアントに発行したクライアントID。
Issuer(clientID).
// sub: デジタル認証アプリサービスがクライアントに発行したクライアントID。
Subject(clientID).
// aud: トークンエンドポイント https://{FQDN}/api/realms/main/protocol/openid-connect/token を設定。
Audience([]string{tokenURL}).
// jti: 任意のUUIDや、client_id + timestamp などを組み合わせたトークンを一意に識別するための識別子を設定。
JwtID(fmt.Sprintf("%s_%d", clientID, time.Now().UnixNano())).
IssuedAt(time.Now()).
Expiration(time.Now().Add(5 * time.Minute)).
Build()
if err != nil {
return "", fmt.Errorf("failed to build token: %w", err)
}
signed, err := jwt.Sign(tok, jwt.WithKey(jwa.ES256, key))
if err != nil {
return "", fmt.Errorf("failed to sign token: %w", err)
}
return string(signed), nil
}
func run() error {
key, err := jwk.ParseKey([]byte(jwkJSON))
if err != nil {
return fmt.Errorf("failed to parse JWK: %w", err)
}
// client_assertion を生成します。
clientAssertion, err := generateJWT(clientID, tokenURL, key)
if err != nil {
return fmt.Errorf("failed to generate JWT: %w", err)
}
// リクエストのパラメータを設定
reqBody := url.Values{}
// 認可コードフローの場合は `grant_type` に `authorization_code` を設定します。
reqBody.Set("grant_type", "authorization_code")
reqBody.Set("client_id", clientID)
reqBody.Set("code", authCode)
reqBody.Set("redirect_uri", redirectURI)
reqBody.Set("code_verifier", cv)
reqBody.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
reqBody.Set("client_assertion", clientAssertion)
// トークンエンドポイントにリクエストを送信します。
resp, err := http.Post(tokenURL, "application/x-www-form-urlencoded", bytes.NewBufferString(reqBody.Encode()))
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
// レスポンスを読み込みます。
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
// レスポンスを表示します。
fmt.Println(string(respBody))
return nil
}
func main() {
if err := run(); err != nil {
panic(err)
}
}
実行結果
トークンの取得に成功すると、以下のようなレスポンスが返されます:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhVFN4RHpxM1B6c2dhak0wN3YzWmxGSm8tajBWUGdaVWQ1d0NPOUJydkx3In0...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmZDMzN2ZkMS1lMzU5LTQyMjEtODM1NS1lMWVhYzBhZTZjZTUifQ...",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhVFN4RHpxM1B6c2dhak0wN3YzWmxGSm8tajBWUGdaVWQ1d0NPOUJydkx3In0...",
"not_before_policy": 0,
"session_state": "7bc46d4f-7184-4649-9d10-96a03ce50003",
"scope": "openid offline_access sign"
}
次のステップ
トークンの取得に成功したら、署名トランザクション終了リクエストを送信し、署名結果を取得しましょう。
その他、デジタル認証アプリサービス API の使い方やエラーの詳細等については、デジタル認証アプリのマニュアルをご覧ください。