JSON Web Token (JWT) について
アプリとしての認証やインストール用のアクセス トークンを生成するには、JSON Web Token (JWT) を生成する必要があります。 REST API のエンドポイントが JWT を必要とする場合、そのエンドポイントのドキュメントで、JWT を使ってエンドポイントにアクセスする必要があることが示されています。
JWT は RS256 アルゴリズムを使って署名される必要があり、次のような要求を含む必要があります。
| 要求 | 意味 | 詳細 | 
|---|---|---|
| iat | 発行時刻 | JWT が作成された時刻。 クロック ドリフトから保護するために、これを過去 60 秒に設定し、サーバーの日付と時刻が正確に (たとえば、ネットワーク タイム プロトコルを使って) 設定されるようにすることをお勧めします。 | 
| exp | 有効期限が切れるタイミング | JWT の有効期限。これを過ぎると、インストール トークンの要求に使うことができません。 時間は 10 分以内にする必要があります。 | 
| iss | 発行者 | GitHub App のクライアント ID またはアプリケーション ID。 この値は、JWT の署名を検証するための適切な公開キーの検索に使用されます。 アプリ ID は、お使いの GitHub App の [settings] ページで確認できます。 クライアント ID を使用することをお勧めします。 GitHub App の [Settings] ページに移動する方法の詳細については、「GitHub App 登録の変更」を参照してください。 | 
| alg | メッセージ認証コード アルゴリズム | JWT は RS256アルゴリズムを使って署名しなければならないので、これはRS256である必要があります。 | 
JWT を使うには、API 要求の Authorization ヘッダーに JWT を渡します。 次に例を示します。
curl --request GET \
--url "https://api.github.com/app" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer YOUR_JWT" \
--header "X-GitHub-Api-Version: 2022-11-28"
ほとんどの場合は、Authorization: Bearer または Authorization: token を使用してトークンを渡すことができます。 ただし、JSON Web トークン (JWT) を渡す場合は、Authorization: Bearer を使用する必要があります。
JSON Web Token (JWT) を生成する
ほとんどのプログラミング言語には、JWT を生成できるパッケージがあります。 いずれの場合も、秘密キーと GitHub App の ID を用意する必要があります。 秘密キーの生成に関する詳細については、「GitHub Apps の秘密キーの管理」を参照してください。 アプリの ID は、GET /app REST API エンドポイントで確認できます。 詳細については、REST API のドキュメントの「アプリ」を参照してください。
メモ
JWT を作成する代わりに、GitHub の Octokit SDK を使って、アプリとして認証できます。 この SDK は JWT の生成を処理し、トークンの有効期限が切れると JWT を再生成します。 詳細については、「REST API と JavaScript を使用したスクリプト」を参照してください。
例: Ruby を使った JWT の生成
メモ
このスクリプトを使うには、gem install jwt を実行して jwt パッケージをインストールする必要があります。
次の例では、YOUR_PATH_TO_PEM を秘密キーが配置されているファイル パスに置き換えます。 YOUR_CLIENT_ID は、実際のアプリの ID に置き換えます。 YOUR_PATH_TO_PEM と YOUR_CLIENT_ID の値は必ず二重引用符で囲んでください。
require 'openssl'
require 'jwt'  # https://rubygems.org/gems/jwt
# Private key contents
private_pem = File.read("YOUR_PATH_TO_PEM")
private_key = OpenSSL::PKey::RSA.new(private_pem)
# Generate the JWT
payload = {
  # issued at time, 60 seconds in the past to allow for clock drift
  iat: Time.now.to_i - 60,
  # JWT expiration time (10 minute maximum)
  exp: Time.now.to_i + (10 * 60),
  
# GitHub App's client ID
  iss: "YOUR_CLIENT_ID"
}
jwt = JWT.encode(payload, private_key, "RS256")
puts jwt
例: Python を使った JWT の生成
メモ
このスクリプトを使うには、pip install PyJWT cryptography を実行して PyJWT と cryptography の両パッケージをインストールする必要があります。
#!/usr/bin/env python3
import sys
import time
import jwt
# Get PEM file path
if len(sys.argv) > 1:
    pem = sys.argv[1]
else:
    pem = input("Enter path of private PEM file: ")
# Get the Client ID
if len(sys.argv) > 2:
    client_id = sys.argv[2]
else:
    client_id = input("Enter your Client ID: ")
# Open PEM
with open(pem, 'rb') as pem_file:
    signing_key = pem_file.read()
payload = {
    # Issued at time
    'iat': int(time.time()),
    # JWT expiration time (10 minutes maximum)
    'exp': int(time.time()) + 600,
    
    # GitHub App's client ID
    'iss': client_id
}
# Create JWT
encoded_jwt = jwt.encode(payload, signing_key, algorithm='RS256')
print(f"JWT: {encoded_jwt}")
#!/usr/bin/env python3
import sys
import time
import jwt
# Get PEM file path
if len(sys.argv) > 1:
    pem = sys.argv[1]
else:
    pem = input("Enter path of private PEM file: ")
# Get the Client ID
if len(sys.argv) > 2:
    client_id = sys.argv[2]
else:
    client_id = input("Enter your Client ID: ")
# Open PEM
with open(pem, 'rb') as pem_file:
    signing_key = pem_file.read()
payload = {
    # Issued at time
    'iat': int(time.time()),
    # JWT expiration time (10 minutes maximum)
    'exp': int(time.time()) + 600,
    
    # GitHub App's client ID
    'iss': client_id
}
# Create JWT
encoded_jwt = jwt.encode(payload, signing_key, algorithm='RS256')
print(f"JWT: {encoded_jwt}")
このスクリプトでは、秘密キーが格納されているファイル パスとアプリの ID を入力するように求められます。 または、スクリプトを実行するときにインライン引数としてこれらの値を渡すこともできます。
例: Bash を使った JWT の生成
メモ
このスクリプトを実行するときは、クライアント ID と、秘密キーが引数として格納されているファイル パスを渡す必要があります。
#!/usr/bin/env bash
set -o pipefail
client_id=$1 # Client ID as first argument
pem=$( cat $2 ) # file path of the private key as second argument
now=$(date +%s)
iat=$((${now} - 60)) # Issues 60 seconds in the past
exp=$((${now} + 600)) # Expires 10 minutes in the future
b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }
header_json='{
    "typ":"JWT",
    "alg":"RS256"
}'
# Header encode
header=$( echo -n "${header_json}" | b64enc )
payload_json="{
    \"iat\":${iat},
    \"exp\":${exp},
    \"iss\":\"${client_id}\"
}"
# Payload encode
payload=$( echo -n "${payload_json}" | b64enc )
# Signature
header_payload="${header}"."${payload}"
signature=$(
    openssl dgst -sha256 -sign <(echo -n "${pem}") \
    <(echo -n "${header_payload}") | b64enc
)
# Create JWT
JWT="${header_payload}"."${signature}"
printf '%s\n' "JWT: $JWT"
#!/usr/bin/env bash
set -o pipefail
client_id=$1 # Client ID as first argument
pem=$( cat $2 ) # file path of the private key as second argument
now=$(date +%s)
iat=$((${now} - 60)) # Issues 60 seconds in the past
exp=$((${now} + 600)) # Expires 10 minutes in the future
b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }
header_json='{
    "typ":"JWT",
    "alg":"RS256"
}'
# Header encode
header=$( echo -n "${header_json}" | b64enc )
payload_json="{
    \"iat\":${iat},
    \"exp\":${exp},
    \"iss\":\"${client_id}\"
}"
# Payload encode
payload=$( echo -n "${payload_json}" | b64enc )
# Signature
header_payload="${header}"."${payload}"
signature=$(
    openssl dgst -sha256 -sign <(echo -n "${pem}") \
    <(echo -n "${header_payload}") | b64enc
)
# Create JWT
JWT="${header_payload}"."${signature}"
printf '%s\n' "JWT: $JWT"
例: PowerShell を使った JWT の生成
次の例では、YOUR_PATH_TO_PEM を秘密キーが配置されているファイル パスに置き換えます。 YOUR_CLIENT_ID は、実際のアプリの ID に置き換えます。 YOUR_PATH_TO_PEM の値は必ず二重引用符で囲んでください。
#!/usr/bin/env pwsh
$client_id = YOUR_CLIENT_ID
$private_key_path = "YOUR_PATH_TO_PEM"
$header = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{
  alg = "RS256"
  typ = "JWT"
}))).TrimEnd('=').Replace('+', '-').Replace('/', '_');
$payload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{
  iat = [System.DateTimeOffset]::UtcNow.AddSeconds(-10).ToUnixTimeSeconds()
  exp = [System.DateTimeOffset]::UtcNow.AddMinutes(10).ToUnixTimeSeconds()
   iss = $client_id 
}))).TrimEnd('=').Replace('+', '-').Replace('/', '_');
$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportFromPem((Get-Content $private_key_path -Raw))
$signature = [Convert]::ToBase64String($rsa.SignData([System.Text.Encoding]::UTF8.GetBytes("$header.$payload"), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)).TrimEnd('=').Replace('+', '-').Replace('/', '_')
$jwt = "$header.$payload.$signature"
Write-Host $jwt
#!/usr/bin/env pwsh
$client_id = YOUR_CLIENT_ID
$private_key_path = "YOUR_PATH_TO_PEM"
$header = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{
  alg = "RS256"
  typ = "JWT"
}))).TrimEnd('=').Replace('+', '-').Replace('/', '_');
$payload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{
  iat = [System.DateTimeOffset]::UtcNow.AddSeconds(-10).ToUnixTimeSeconds()
  exp = [System.DateTimeOffset]::UtcNow.AddMinutes(10).ToUnixTimeSeconds()
   iss = $client_id 
}))).TrimEnd('=').Replace('+', '-').Replace('/', '_');
$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportFromPem((Get-Content $private_key_path -Raw))
$signature = [Convert]::ToBase64String($rsa.SignData([System.Text.Encoding]::UTF8.GetBytes("$header.$payload"), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)).TrimEnd('=').Replace('+', '-').Replace('/', '_')
$jwt = "$header.$payload.$signature"
Write-Host $jwt