概要
GoogleAPIを使う際、多くの場合は「ユーザごとに認証させてアクセストークンを発行し、リクエストに利用する」という流れですが、APIによってはわざわざユーザ個別にアクセストークンを発行させる必要がないケースもあります。
そんなケースでは「Service Accounts」という方式を使い、サービス側でアクセストークンを発行してAPIを利用します。以下の様な流れです。
今回は例としてGoogleDriveにアクセスしてみます。
手順
- Developer Consoleでアプリ作成し、「Service Accounts」を選択して、秘密鍵を取得
- 秘密鍵で署名したJWTを作る
- Googleのトークンエンドポイントを叩いてアクセストークンを取得
- 取得したアクセストークンでAPIを叩く
環境
- Node.js v0.12.0
Developer Consoleでアプリを作る
プロジェクトの作成
利用するAPIをONに
検索して
有効にするボタンを押します。
認証情報で新しいクライアントIDを作成
左メニューの認証情報を選択し、クライアントIDを作成
サービスアカウントを選択
勝手に公開鍵、秘密鍵のペアが作成され、秘密鍵がダウンロードされます。
クライアントIDの作成が完了すると情報が表示されるようになります。
秘密鍵からJWTを作成する
JWTフォーマット
JWTは以下のフォーマットです。
{base64エンコードしたheader}.{base64エンコードしたclaims}.{署名}
ヘッダ
パラメータ | 説明 |
---|---|
alg | 署名アルゴリズム。RSA-SHA256 を使用 |
typ | タイプ。JWT に設定 |
実際には以下の形です。
{ "alg": "RS256", "typ": "JWT" }
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
クレーム
パラメータ | 説明 |
---|---|
iss | Developer Consoleで作ったクライアントIDの部分に書いてあるメールアドレス。xxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxx@developer.gserviceaccount.com の形 |
scope | 利用するAPIスコープ |
aud | クライアント識別子。アクセストークンを発行する場合はhttps://www.googleapis.com/oauth2/v3/token |
exp | トークンの有効期限。最大でiat から1時間分まで |
iat | トークンの発行日 |
実際には以下の形です。
{ "iss": "xxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxx@developer.gserviceaccount.com", "scope": "https://www.googleapis.com/auth/drive", "aud": "https://www.googleapis.com/oauth2/v3/token", "exp": 1433469193, "iat": 1433465593 }
eyJpc3MiOiI2jMzNDUzNDI1ODQtNmgyaW0xamIwOHJsbWVodTFuOGtxNm43MWpvYmlwNGVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZHJpdmUiLCJhdWQiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjMvdG9rZW4iLCJleHAiOjE0MzM0MjgyMjcsImlhdCI6MTQzMzQyNDYyN30=
署名
以下の文字列を、秘密鍵を使ってRSA-SHA256
形式で署名します。
{base64エンコードしたheader}.{base64エンコードしたclaims}
Developer Console からダウンロードしたJSONのprivate_key
にある秘密鍵を使用します。
生成されたJWT
実際の値は以下のようになります。
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2jMzNDUzNDI1ODQtNmgyaW0xamIwOHJsbWVodTFuOGtxNm43MWpvYmlwNGVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZHJpdmUiLCJhdWQiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjMvdG9rZW4iLCJleHAiOjE0MzM0MjgyMjcsImlhdCI6MTQzMzQyNDYyN30=.kRH1WwWBg1pwx5lKrLjFzD444jxasDZGlfd5xGK9BX1GmLWQTavRr15jZu0FcGNUYPyZ1u+wsSRYOTk91ePIEu8hKb6HzD1nz6QrxxM48xJtDGFtwxZC57ZsqiR+qLKt79PdndrcCFeItHub3L7v/7QfbeaR7T4CRMvyyLfiQs=
JWTをリクエストにつけてアクセストークンを取得する
アクセストークンを取得するのに必要なパラメータは以下です。
パラメータ | 説明 |
---|---|
assertion | JWT |
grant_type | urn:ietf:params:oauth:grant-type:jwt-bearer URL-encoded必須 |
URL-encoded必須なので、Content-Type: application/x-www-form-urlencoded
のヘッダを付けてください。
以下のBodyをhttps://www.googleapis.com/oauth2/v3/token
に対してPOSTメソッドでリクエストを送ります。
{ "assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2jMzNDUzNDI1ODQtNmgyaW0xamIwOHJsbWVodTFuOGtxNm43MWpvYmlwNGVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZHJpdmUiLCJhdWQiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjMvdG9rZW4iLCJleHAiOjE0MzM0MjgyMjcsImlhdCI6MTQzMzQyNDYyN30=.kRH1WwWBg1pwx5lKrLjFzD444jxasDZGlfd5xGK9BX1GmLWQTavRr15jZu0FcGNUYPyZ1u+wsSRYOTk91ePIEu8hKb6HzD1nz6QrxxM48xJtDGFtwxZC57ZsqiR+qLKt79PdndrcCFeItHub3L7v/7QfbeaR7T4CRMvyyLfiQs=", "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer" }
プログラム作成&実行
あらかじめ必要なライブラリをインストールしておきます。
$ npm install superagent $ npm install crypto
今までの説明を反映したプログラムは以下の通りです。
var request = require('superagent'); var crypto = require('crypto'); var fs = require('fs'); var privateKey = require('./key.json'); // private key generated on Developer Console. var driveScope = 'https://www.googleapis.com/auth/drive'; var tokenEndpoint = 'https://www.googleapis.com/oauth2/v3/token'; var grantType = 'urn:ietf:params:oauth:grant-type:jwt-bearer'; var now = Math.floor( new Date().getTime() / 1000 ); var header = { alg: 'RS256', typ: 'JWT' }; var claims = { iss: 'xxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxx@developer.gserviceaccount.com', scope: driveScope, aud: tokenEndpoint, exp: now + 3600, // maximum expiry date is 1 hour after the issued time. iat: now }; var body = { assertion: createJWT(header, claims), grant_type: grantType } // send request to get access token request.post(tokenEndpoint) .set('Content-Type', 'application/x-www-form-urlencoded') // you must set this header. .send(body) .end(function(err, res){ console.log('result: ', res.body); }); function createJWT(header, claims) { var encodedHeader = new Buffer(JSON.stringify(header)).toString('base64'); var encodedClaims = new Buffer(JSON.stringify(claims)).toString('base64'); var sign = crypto.createSign('RSA-SHA256'); var data = new Buffer(encodedHeader + '.' + encodedClaims); sign.update(data); signature = sign.sign(new Buffer(privateKey.private_key), 'base64'); // JWT format is '{base64 encoded header}.{base64 encoded claims}.{signature}' return [encodedHeader, encodedClaims, signature].join('.'); }
成功すると以下のようになります。
{ "access_token": "ya29.iAGB3kySHSQDUEo4NPMwWEgZ8kjOMj1F0T5uibEA_9TR2p1cd8LFjM2FLhyw3Re5RE4j-42BPvn9g", "token_type": "Bearer", "expires_in": 3600 }
失敗すると以下のようになります。
{ "error": "invalid_grant", "error_description": "Bad Request" }
APIを叩く
ファイルの一覧表示するAPIを叩いてみます。
$ curl -H "Authorization: OAuth ya29.iAGB3kySHSQDUEo4NPMwWEgZ8kjOMj1F0T5uibEA_9TR2p1cd8LFjM2FLhyw3Re5RE4j-42BPvn9g" \ https://www.googleapis.com/drive/v2/files
アクセストークンが有効期限内であれば一覧を表示してくれます。