GoでLINE Messaging APIのチャネルアクセストークンv2.1を取得する
やりたいこと
Goを使って、LINE Messaging API[1]を叩くのが目標です。
LINE Messaging APIを使うには、チャネルアクセストークンが必要になるのですが、この取得方法には大きく分けて以下の3つあります[2]。
- 任意の有効期間を指定できるチャネルアクセストークン(チャネルアクセストークンv2.1)
- 短期のチャネルアクセストークン
- 長期のチャネルアクセストークン
この中で1番の方法が推奨されているので、今回は、この方法でチャネルトークンをGo言語で取得します。
基本的には公式ドキュメントを参考に実装していけば良いのですが、いくつか躓いたポイントがあるので、その解決策を重点的に書いていきたいと思います。
躓いたポイント
- JWT(JSON Web Token)のヘッダーに
kid
プロパティと、その値を追加する方法 - JWTのペイロードに必要なプロパティを追加する方法
この2点を踏まえながら、以下でアクセストークンv2.1の発行方法を書きます。
アクセストークンv2.1発行方法
必要なパッケージ
チャネルアクセストークンv2.1を発行する[3]に記載されているGoのパッケージgithub.com/lestrrat-go/jwx/v2
を使います。
以下のコマンドでパッケージを取得してください。
go get github.com/lestrrat-go/jwx/v2
アサーション署名キーの取得
JWTを作成するためには、アサーション署名キーが必要になります。
チャネルアクセストークンv2.1を発行する[3:1]には、取得方法についてGoのライブラリで生成する、Pythonのライブラリで生成する、ブラウザで生成するの3つが紹介されていますが、ブラウザで生成するが最も簡単なので、これでアサーション署名キーを生成します。
生成されるアサーション署名キーの例は、ブラウザで生成するに掲載されているので、参考にしてください。
kid
を取得する。
公開鍵を登録し、前の節で取得した、アサーション署名キーのうち、公開鍵をLINE Developerコンソールに登録することでkid
を取得できます。
手順については公開鍵を登録し、kid
を取得するを参照してください。
Goを使ってJWTを生成する
では、Goを使ってJWTを生成します。
LINEのドキュメント[3:2]には、Node.jsのライブラリとPythonのライブラリを使う方法しか書いていません。
ここでは、jwt.ioに公開されているライブラリの一覧から、LINEのドキュメント[3:3]でも紹介されていたlestrrat-go/jwxを使います。
今回作成し、動作確認したコードは以下になります。
private.keyに先の節「アサーション署名キーの取得」で取得した秘密鍵をコピペして、更にkid
を追加して保存します。
その上で、以下のコードを実行してください。
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/joho/godotenv"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
)
type Foo struct {
Token string `json:"access_token"`
Type string `json:"token_type"`
Exp int64 `json:"expires_in"`
Id string `json:"key_id"`
}
func main() {
// 環境変数ファイルの読み込み
godotenv.Load(".env")
// 秘密鍵のファイルを開く
f, err := os.Open("private.key")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// ファイルから秘密鍵の読み込み
b, err := ioutil.ReadAll(f)
privkey, err := jwk.ParseKey(b)
if err != nil {
fmt.Printf("failed to parse JWK: %s\n", err)
return
}
{
// audプロパティに追加するために、aud変数を作成
var aud []string
aud = append(aud, "https://api.line.me/") // audプロパティの値を追加
// JWTを構成する
tok, err := jwt.NewBuilder().
Subject(os.Getenv("CHID")). // subプロパティ、チャネルIDを入れる
Issuer(os.Getenv("CHID")). // issプロパティ、チャネルIDを入れる
Audience(aud). // audプロパティ、先程作った値audを入れる
Expiration(time.Now().Add(30 * time.Minute)). // expプロパティ、JWTの有効期間、最大30分を入れる
Build()
if err != nil {
fmt.Printf("failed to build token: %s\n", err)
return
}
// token_expプロパティはメソッドが用意されてないので、.Setで追加。
tok.Set("token_exp", 60*60*24*30) // token_expプロパティ、チャネルアクセストークンの有効期間を指定
// JWTを発行する
signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, privkey)) // signedにJWTがエンコードされ代入される
if err != nil {
fmt.Printf("failed to sign token: %s\n", err)
return
}
fmt.Println("🏷 JWT")
fmt.Println(string(signed)) // JWTの確認
// チャネルアクセストークンv2.1を発行するリクエストの作成
// 参考)https://developers.line.biz/ja/reference/messaging-api/#issue-channel-access-token-v2-1
form := url.Values{}
form.Set("grant_type", "client_credentials")
form.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
form.Add("client_assertion", string(signed))
body := strings.NewReader(form.Encode()) // リクエストのbodyを作成
// リクエストの作成
req, err := http.NewRequest(http.MethodPost, "https://api.line.me/oauth2/v2.1/token", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 作成したリクエストの送信
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
// レスポンスの解析
var r io.Reader = res.Body
var foo Foo
err = json.NewDecoder(r).Decode(&foo)
if err != nil {
log.Fatal(err)
}
bytes, err := json.Marshal(foo)
fmt.Println("🎁チャネルアクセストークンを含むペイロード")
fmt.Println(string(bytes))
fmt.Println("🔑チャネルアクセストークン")
fmt.Println(foo.Token)
}
}
ここで、躓いたポイントについて解説します。
kid
プロパティと、その値を追加する方法
1. JWT(JSON Web Token)のヘッダーにこれは、Goのコード上で追加しようと思ったのですが、うまくいきません。
色々と、ドキュメントを読んでいると、秘密鍵にkid
というプロパティがあると、自動的に追加されそうだということがわかりました。
そのため、先の節で取得したkid
を、秘密鍵に追加します。例えばこんな感じです。
{
"alg": "RS256",
"d": "GaDzOmc4......",
"dp": "WAByrYmh......",
"dq": "WLwjYun0......",
"e": "AQ......",
"ext": true,
"key_ops": [
"sign"
],
"kid": "ここにkidで取得した値を入れるよ!",
"kty": "RSA",
"n": "vsbOUoFA......",
"p": "5QJitCu9......",
"q": "1ULfGui5......",
"qi": "2cK4apee......"
}
この秘密鍵ファイルを用意した上で、上で紹介したコードを実行して得たJWTを、以下のサイトのEncodedに入れてもらうと、Headerにkid
が追加されているのが確認できると思います。
2. JWTのペイロードに必要なプロパティを追加する方法
JWTのペイロードに必要なプロパティを追加する方法は、既にコードで示しましたが、jwt.NewBuilder()
のメソッドチェーンを使うのが簡単です。もしも、用意されていないプロパティであれば、JWTに.Set()
を使って追加できます。
例えばこんな感じです。
// JWTを構成する
tok, err := jwt.NewBuilder().
Subject(os.Getenv("CHID")). // subプロパティ、チャネルIDを入れる
Issuer(os.Getenv("CHID")). // issプロパティ、チャネルIDを入れる
Audience(aud). // audプロパティ、先程作った値audを入れる
Expiration(time.Now().Add(30 * time.Minute)). // expプロパティ、JWTの有効期間、最大30分を入れる
Build()
if err != nil {
fmt.Printf("failed to build token: %s\n", err)
return
}
// token_expプロパティはメソッドが用意されてないので、.Setで追加。
tok.Set("token_exp", 60*60*24*30) // token_expプロパティ、チャネルアク
実行結果
実行結果はこんな感じです。
まとめと感想
APIを使うとき、認証と認可のところが一番苦労します。
続きも頑張ってやっていきたいです。
もしも、「ここまずいよ!」とか「分からない!」という所があれば、コメントでご指摘いただけると幸いです。
参考
コードを作る上で参考にしたサイトを紹介します。
-
LINE Messaging API / LINE https://developers.line.biz/ja/docs/messaging-api/ (2022-09-28閲覧) ↩︎
-
チャネルアクセストークン / LINE https://developers.line.biz/ja/docs/messaging-api/channel-access-tokens/ (2022-09-28閲覧) ↩︎
-
チャネルアクセストークンv2.1を発行する / LINE https://developers.line.biz/ja/docs/messaging-api/generate-json-web-token/ (2022-09-28閲覧) ↩︎ ↩︎ ↩︎ ↩︎
Discussion