LINE WORKS Service Account 認証 (JWT) の確認ポイント (RFC に準拠しましょう)


# LINE WORKS Service Account 認証 (JWT) の確認ポイント

LINE WORKS API の Service Account 認証のエラーで悩んだら、JWT (JSON Web Token) の RFC 準拠を確認してみましょう。RFC 非準拠によるエラーの可能性があります。

LINE WORKS API を利用する際の Service Account 認証では、JWT を用いてトークン生成を行います。JWT の取り扱いにおいては、RFC-7519 (JWT)、RFC-7515 (JWS)、および RFC-7523 (JWT Profile for OAuth 2.0) に準拠する必要があります。これにより、標準に沿ったトークン生成が可能となり、LINE WORKS API の認証が正しく行われます。

## 確認したいポイント

### 1. Base64 エンコード

JWT のヘッダーとペイロード部分は、「Base64 URL エンコード」でエンコードする必要がありますが、一般的な Base64 エンコードを用いるケースが見受けられます。

#### RFC-7515 (JSON Web Signature) の該当箇所
```
Base64url Encoding
...
No padding (e.g., using "=") characters are used in base64url-encoded data.
```
例えば、Google Apps Script (GAS) を使用する場合、Utilities.base64EncodeWebSafe を利用します。これは、URL セーフな Base64 エンコードを行い、`+` を `-`、`/` を `_` に置き換え、さらに不要なパディング文字である `=` を削除 します。

もし `Utilities.base64Encode` を使用すると、URL セーフではない形式でエンコードするため、JWT の仕様に準拠しません。

- **RFC 準拠**:
```javascript
var encodedHeader = Utilities.base64EncodeWebSafe(JSON.stringify(header));
var encodedPayload = Utilities.base64EncodeWebSafe(JSON.stringify(payload));
```

- **非準拠**:

```javascript
var encodedHeader = Utilities.base64Encode(JSON.stringify(header));
var encodedPayload = Utilities.base64Encode(JSON.stringify(payload));
```
---

### 2. Claim Set (`exp`, `iat`) の型

`exp` (有効期限) や `iat` (発行時刻) は、UNIX タイムスタンプとして数値型で記述する必要があります。しかし、これを文字列型で指定してしまう例があります。

#### 誤りの例
```json
{
  "exp": "1672531200",
  "iat": "1672527600"
}
```

#### 正しい方法
```json
{
  "exp": 1672531200,
  "iat": 1672527600
}
```

#### RFC-7519 (JSON Web Token) の該当箇所
```
The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The value MUST be a number representing seconds since the epoch.
```
```
The "iat" (issued at) claim identifies the time at which the JWT was issued. The value MUST be a number representing seconds since the epoch.
```
## JWT の生成方法例

以下に、Python と Google Apps Script を使用した正しい JWT 生成の例を示します。

### Python の例
```python
import jwt
import time

# JWT ヘッダー
header = {
    "alg": "RS256",
    "typ": "JWT"
}

# JWT ペイロード
payload = {
    "iss": "your_client_id",
    "sub": "your_service_account",
    "iat": int(time.time()),  # 発行時刻
    "exp": int(time.time()) + 3600  # 有効期限
}

# 秘密鍵
private_key = """-----BEGIN PRIVATE KEY-----
your_private_key
-----END PRIVATE KEY-----"""

# JWT 生成
token = jwt.encode(payload, private_key, algorithm="RS256", headers=header)

print("Generated JWT:")
print(token)
```

---

### Google Apps Script の例
```javascript
function createJWT() {
  var header = {
    "alg": "RS256",
    "typ": "JWT"
  };

  var payload = {
    "iss": "your_client_id",
    "sub": "your_service_account",
    "iat": Math.floor(Date.now() / 1000),  // 発行時刻 (数値型)
    "exp": Math.floor(Date.now() / 1000) + 3600  // 有効期限 (数値型)
  };

  var encodedHeader = Utilities.base64EncodeWebSafe(JSON.stringify(header));
  var encodedPayload = Utilities.base64EncodeWebSafe(JSON.stringify(payload));

  var signature = Utilities.computeRsaSha256Signature(encodedHeader + "." + encodedPayload, privateKey);
  var encodedSignature = Utilities.base64EncodeWebSafe(signature);

  return encodedHeader + "." + encodedPayload + "." + encodedSignature;
}
```

## まとめ

JWT を正しく生成するためには、以下のポイントを守ることが重要です。

1. **Base64 URL エンコードを使用**  
   一般的な Base64 エンコードではなく、URL セーフな形式を使用する。

2. **Claim Set (`exp`, `iat`) を数値型で指定**  
   RFC に準拠するため、UNIX タイムスタンプを使用する。

これらの実装ミスを避けることで、LINE WORKS との認証連携を円滑に行うことができます。

## 参考
- [RFC-7515: JSON Web Signature (JWS)](https://datatracker.ietf.org/doc/html/rfc7515)
- [RFC-7519: JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)
- [LINE WORKS Developer Guide - Service Account 認証 (JWT)](https://developers.worksmobile.com/jp/docs/auth-jwt)