SDKを使用しないJWT
SDKを使用しないJWT
このガイドでは、Box SDKを使用しないJWT認証について説明します。JWTはエンドユーザーによる操作を必要とせず、Box APIで直接認証するよう設計されています。
このトークンの使用方法を確認するには、APIコールの実行に関するガイドを参照してください。
キーペアの使用
公開キーと秘密キーのペアを使用してアプリケーションのIDを確認する場合は、以下の手順に従います。
前提条件
- 開発者コンソールでJWT認証を使用するカスタムアプリケーション
config.json
という名前の秘密キー構成ファイル (開発者コンソールの [構成] タブからダウンロード可能)- Box管理コンソールでアプリケーションが承認されていること
1. JSON構成を読み取る
config.json
ファイルには、アプリケーションの秘密キーと、認証に必要なその他の詳細が含まれています。このファイルの例を以下に示します。
{
"boxAppSettings": {
"clientID": "abc...123",
"clientSecret": "def...234",
"appAuth": {
"publicKeyID": "abcd1234",
"privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n....\n-----END ENCRYPTED PRIVATE KEY-----\n",
"passphrase": "ghi...345"
}
},
"enterpriseID": "1234567"
}
このオブジェクトをアプリケーションで使用するには、ファイルから読み取る必要があります。
using System;
using System.IO;
using Newtonsoft.Json;
class Config
{
public class BoxAppSettings {
public class AppAuth {
public string privateKey { get; set; }
public string passphrase { get; set; }
public string publicKeyID { get; set; }
}
public string clientID { get; set; }
public string clientSecret { get; set; }
public AppAuth appAuth { get; set; }
}
public string enterpriseID { get; set; }
public BoxAppSettings boxAppSettings { get; set; }
}
var reader = new StreamReader("config.json");
var json = reader.ReadToEnd();
var config = JsonConvert.DeserializeObject<Config>(json);
import java.io.FileReader;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
class Config {
class BoxAppSettings {
class AppAuth {
String privateKey;
String passphrase;
String publicKeyID;
}
String clientID;
String clientSecret;
AppAuth appAuth;
}
BoxAppSettings boxAppSettings;
String enterpriseID;
}
FileReader reader = new FileReader("config.json");
Gson gson = new GsonBuilder().create();
Config config = (Config) gson.fromJson(reader, Config.class);
import json
import os
config = json.load(open('config.json'))
const fs = require("fs");
const config = JSON.parse(fs.readFileSync("config.json"));
require 'json'
config = JSON.parse(
File.read('config.json')
)
$json = file_get_contents('config.json');
$config = json_decode($json);
2. 秘密キーを復号化する
JWTアサーションを作成するために、アプリケーションでは構成オブジェクトにある秘密キーが必要になります。この秘密キーは暗号化されており、ロックを解除するにはパスコードが必要です。暗号化されたキーとパスコードは両方とも、構成オブジェクトで指定されています。
using System.Security.Cryptography;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
// https://www.bouncycastle.org/csharp/index.html
class PasswordFinder : IPasswordFinder
{
private string password;
public PasswordFinder(string _password) { password = _password; }
public char[] GetPassword() { return password.ToCharArray(); }
}
var appAuth = config.boxAppSettings.appAuth;
var stringReader = new StringReader(appAuth.privateKey);
var passwordFinder = new PasswordFinder(appAuth.passphrase);
var pemReader = new PemReader(stringReader, passwordFinder);
var keyParams = (RsaPrivateCrtKeyParameters) pemReader.ReadObject();
public RSA CreateRSAProvider(RSAParameters rp)
{
var rsaCsp = RSA.Create();
rsaCsp.ImportParameters(rp);
return rsaCsp;
}
public RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey)
{
RSAParameters rp = new RSAParameters();
rp.Modulus = privKey.Modulus.ToByteArrayUnsigned();
rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned();
rp.P = privKey.P.ToByteArrayUnsigned();
rp.Q = privKey.Q.ToByteArrayUnsigned();
rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length);
rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length);
rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length);
rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length);
return rp;
}
public byte[] ConvertRSAParametersField(BigInteger n, int size)
{
byte[] bs = n.ToByteArrayUnsigned();
if (bs.Length == size)
return bs;
if (bs.Length > size)
throw new ArgumentException("Specified size too small", "size");
byte[] padded = new byte[size];
Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
return padded;
}
var key = CreateRSAProvider(ToRSAParameters(keyParams));
import java.io.StringReader;
import java.security.PrivateKey;
import java.security.Security;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
// https://www.bouncycastle.org/java.html
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(
new StringReader(config.boxAppSettings.appAuth.privateKey)
);
Object keyPair = pemParser.readObject();
pemParser.close();
char[] passphrase = config.boxAppSettings.appAuth.passphrase.toCharArray();
JceOpenSSLPKCS8DecryptorProviderBuilder decryptBuilder =
new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC");
InputDecryptorProvider decryptProvider
= decryptBuilder.build(passphrase);
PrivateKeyInfo keyInfo
= ((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(decryptProvider);
PrivateKey key = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
appAuth = config["boxAppSettings"]["appAuth"]
privateKey = appAuth["privateKey"]
passphrase = appAuth["passphrase"]
# https://cryptography.io/en/latest/
key = load_pem_private_key(
data=privateKey.encode('utf8'),
password=passphrase.encode('utf8'),
backend=default_backend(),
)
let key = {
key: config.boxAppSettings.appAuth.privateKey,
passphrase: config.boxAppSettings.appAuth.passphrase,
};
require "openssl"
appAuth = config['boxAppSettings']['appAuth']
key = OpenSSL::PKey::RSA.new(
appAuth['privateKey'],
appAuth['passphrase']
)
$private_key = $config->boxAppSettings->appAuth->privateKey;
$passphrase = $config->boxAppSettings->appAuth->passphrase;
$key = openssl_pkey_get_private($private_key, $passphrase);
3. JWTアサーションを作成する
Box APIで認証するために、アプリケーションは、アクセストークンと交換できる署名済みのJWTアサーションを作成する必要があります。
JWTアサーションは、暗号化されたJSONオブジェクトで、header
、claims
、およびsignature
で構成されます。最初にclaims
を作成します。これは、payload
とも呼ばれる場合もあります。
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Collections.Generic;
byte[] randomNumber = new byte[64];
RandomNumberGenerator.Create().GetBytes(randomNumber);
var jti = Convert.ToBase64String(randomNumber);
DateTime expirationTime = DateTime.UtcNow.AddSeconds(45);
var claims = new List<Claim>{
new Claim("sub", config.enterpriseID),
new Claim("box_sub_type", "enterprise"),
new Claim("jti", jti),
};
import org.jose4j.jwt.JwtClaims;
String authenticationUrl = "https://api.box.com/oauth2/token";
JwtClaims claims = new JwtClaims();
claims.setIssuer(config.boxAppSettings.clientID);
claims.setAudience(authenticationUrl);
claims.setSubject(config.enterpriseID);
claims.setClaim("box_sub_type", "enterprise");
claims.setGeneratedJwtId(64);
claims.setExpirationTimeMinutesInTheFuture(0.75f);
import time
import secrets
authentication_url = 'https://api.box.com/oauth2/token'
claims = {
'iss': config['boxAppSettings']['clientID'],
'sub': config['enterpriseID'],
'box_sub_type': 'enterprise',
'aud': authentication_url,
'jti': secrets.token_hex(64),
'exp': round(time.time()) + 45
}
const crypto = require("crypto");
const authenticationUrl = "https://api.box.com/oauth2/token";
let claims = {
iss: config.boxAppSettings.clientID,
sub: config.enterpriseID,
box_sub_type: "enterprise",
aud: authenticationUrl,
jti: crypto.randomBytes(64).toString("hex"),
exp: Math.floor(Date.now() / 1000) + 45,
};
require 'securerandom'
authentication_url = 'https://api.box.com/oauth2/token'
claims = {
iss: config['boxAppSettings']['clientID'],
sub: config['enterpriseID'],
box_sub_type: 'enterprise',
aud: authentication_url,
jti: SecureRandom.hex(64),
exp: Time.now.to_i + 45
}
$authenticationUrl = 'https://api.box.com/oauth2/token';
$claims = [
'iss' => $config->boxAppSettings->clientID,
'sub' => $config->enterpriseID,
'box_sub_type' => 'enterprise',
'aud' => $authenticationUrl,
'jti' => base64_encode(random_bytes(64)),
'exp' => time() + 45,
'kid' => $config->boxAppSettings->appAuth->publicKeyID
];
パラメータ | 型 | 説明 |
---|---|---|
iss (必須) | String | BoxアプリケーションのOAuthクライアントID |
sub (必須) | String | Box Enterprise ID (このアプリがそのアプリケーションのサービスアカウントの代わりになる場合) またはユーザーID (このアプリが別のユーザーの代わりになる場合)。 |
box_sub_type (必須) | String | enterprise またはuser (sub クレームでリクエストされているトークンの種類に応じて決定) |
aud (必須) | String | 常にhttps://api.box.com/oauth2/token |
jti (必須) | String | このJWTに対してアプリケーションで指定されたUUID (Universally Unique Identifier)。16文字以上128文字以下の一意の文字列です。 |
exp (必須) | Integer | このJWTが期限切れとなるUnix時間。設定できる最大値は、発行時刻から60秒後です。許容される最大値よりも小さい値を設定することをお勧めします。 |
iat (省略可) | Integer | 発行時刻。トークンは、この時刻より前に使用することはできません。 |
nbf (省略可) | Integer | 開始時刻。トークンの有効期間の開始時刻を指定します。 |
次に、秘密キーを使用してこれらのクレームに署名する必要があります。使用する言語とライブラリに応じて、クレームの署名に使用する暗号化アルゴリズムと公開キーのIDを定義することで、JWTのheader
が構成されます。
using Microsoft.IdentityModel.Tokens;
String authenticationUrl = "https://api.box.com/oauth2/token";
var payload = new JwtPayload(
config.boxAppSettings.clientID,
authenticationUrl,
claims,
null,
expirationTime
);
var credentials = new SigningCredentials(
new RsaSecurityKey(key),
SecurityAlgorithms.RsaSha512
);
var header = new JwtHeader(signingCredentials: credentials);
var jst = new JwtSecurityToken(header, payload);
var tokenHandler = new JwtSecurityTokenHandler();
string assertion = tokenHandler.WriteToken(jst);
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setKey(key);
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA512);
jws.setHeader("typ", "JWT");
jws.setHeader("kid", config.boxAppSettings.appAuth.publicKeyID);
String assertion = jws.getCompactSerialization();
import jwt
keyId = config['boxAppSettings']['appAuth']['publicKeyID']
assertion = jwt.encode(
claims,
key,
algorithm='RS512',
headers={
'kid': keyId
}
)
const jwt = require("jsonwebtoken");
let keyId = config.boxAppSettings.appAuth.publicKeyID;
let headers = {
algorithm: "RS512",
keyid: keyId,
};
let assertion = jwt.sign(claims, key, headers);
require 'jwt'
keyId = appAuth['publicKeyID']
assertion = JWT.encode(claims, key, 'RS512', { kid: keyId })
use \Firebase\JWT\JWT;
$assertion = JWT::encode($claims, $key, 'RS512');
ヘッダーでは、以下のパラメータがサポートされます。
パラメータ | 型 | 説明 |
---|---|---|
algorithm (必須) | String | JWTクレームへの署名に使用する暗号化アルゴリズム。RS256、RS384、RS512のいずれかを指定できます。 |
keyid (必須) | String | JWTへの署名に使用する公開キーのID。必須ではありませんが、アプリケーションに対して複数のキーペアが定義される場合は必須です。 |
4. アクセストークンをリクエストする
最後の手順として、有効期間の短いJWTアサーションを、より有効期間の長いアクセストークンと交換します。これには、アサーションをパラメータに指定してトークンエンドポイントを呼び出します。
using System.Net;
using System.Net.Http;
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>(
"grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
new KeyValuePair<string, string>(
"assertion", assertion),
new KeyValuePair<string, string>(
"client_id", config.boxAppSettings.clientID),
new KeyValuePair<string, string>(
"client_secret", config.boxAppSettings.clientSecret)
});
var client = new HttpClient();
var response = client.PostAsync(authenticationUrl, content).Result;
class Token
{
public string access_token { get; set; }
}
var data = response.Content.ReadAsStringAsync().Result;
var token = JsonConvert.DeserializeObject<Token>(data);
var accessToken = token.access_token;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(
"grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"));
params.add(new BasicNameValuePair(
"assertion", assertion));
params.add(new BasicNameValuePair(
"client_id", config.boxAppSettings.clientID));
params.add(new BasicNameValuePair(
"client_secret", config.boxAppSettings.clientSecret));
CloseableHttpClient httpClient =
HttpClientBuilder.create().disableCookieManagement().build();
HttpPost request = new HttpPost(authenticationUrl);
request.setEntity(new UrlEncodedFormEntity(params));
CloseableHttpResponse httpResponse = httpClient.execute(request);
HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity);
httpClient.close();
class Token {
String access_token;
}
Token token = (Token) gson.fromJson(response, Token.class);
String accessToken = token.access_token;
import json
import requests
params = {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': assertion,
'client_id': config['boxAppSettings']['clientID'],
'client_secret': config['boxAppSettings']['clientSecret']
}
response = requests.post(authentication_url, params)
access_token = response.json()['access_token']
const axios = require("axios");
const querystring = require("querystring");
let accessToken = await axios
.post(
authenticationUrl,
querystring.stringify({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: assertion,
client_id: config.boxAppSettings.clientID,
client_secret: config.boxAppSettings.clientSecret,
})
)
.then((response) => response.data.access_token);
require 'json'
require 'uri'
require 'net/https'
params = URI.encode_www_form({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: assertion,
client_id: config['boxAppSettings']['clientID'],
client_secret: config['boxAppSettings']['clientSecret']
})
uri = URI.parse(authentication_url)
http = Net::HTTP.start(uri.host, uri.port, use_ssl: true)
request = Net::HTTP::Post.new(uri.request_uri)
request.body = params
response = http.request(request)
access_token = JSON.parse(response.body)['access_token']
use GuzzleHttp\Client;
$params = [
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $assertion,
'client_id' => $config->boxAppSettings->clientID,
'client_secret' => $config->boxAppSettings->clientSecret
];
$client = new Client();
$response = $client->request('POST', $authenticationUrl, [
'form_params' => $params
]);
$data = $response->getBody()->getContents();
$access_token = json_decode($data)->access_token;
コードサンプル
このガイドに記載されているコードは、GitHubで入手できます。