PKCE対応のためのcode_challenge生成をフロントエンドで行いたい
- PKCE対応のためのコード生成をフロントエンド側で行う必要が出てきた
- sha256のハッシュ化やランダムな文字列生成のためのライブラリは世にたくさんあるが、外部ライブラリを利用しなくとも実装は可能であると分かった
- 具体的にはweb crypto APIというブラウザ内の機能を使う。その説明はこの記事が詳しい
- 紛らわしいのは、
node.js
にもcyrpto
というAPIが存在すること- ここに私はひっかかった。
node.js
のcrypto
のAPIで実装を進めてしまっていた。node.js
なのでブラウザでは直接実行できない。実行するためにはBrowserify
、streamify
というツールのインストールが必要になってしまう -
Buffer
を使ったエンコード例の記事がweb上には多い。しかし、Buffer
はブラウザでは標準非対応である。ブラウザで動かしたいなら利用を避けたほうがいい - 下記の記事が、両ライブラリの違いについて詳しい
- [Node.js][JavaScript]CryptoAPIの違いでハマったのでまとめ
- ここに私はひっかかった。
- ブラウザでも標準ライブラリだけで、PKCE認証のためのコード生成は行えることを、主要なメッセージとして本記事を投稿する
具体的なコード例
- ここで私の実装例を書きたいところだが、実際はWouterSpaak氏のコード例をほとんど参考にした。WouterSpaakさん、本当にありがとうございます
- WoutrerSpaak氏のStackblitz上で彼のコードが確認できる
- コード例は大体下記のような感じである
challenge-code-service.ts
const private _code_verifier;
get code_verifier {
return _code_verifier;
}
//このメソッドをcode_challengeが欲しいときに呼び出す
createCodeChallengeAsync() {
_code_verifier = randomStr(43); //PKCEでのcode_verifierは43文字以上128文字以下である必要がある
const shaBuffer = await sha256(code_verifier);
const encoded = bufferToBase64UrlEncoded(shaBuffer);
document.getElementById('encoded').textContent = encoded;
}
randomStr(len: number) {
const arr = new Uint8Array(len);
window.crypto.getRandomValues(arr);
return String.fromCharCode(...toCharCodes(arr));
}
toCharCodes(arr: Uint8Array) {
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_~.';
return arr.map(x => validChars.charCodeAt(x % validChars.length));
}
sha256 (message: string) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
return window.crypto.subtle.digest('SHA-256', data);
}
bufferToBase64UrlEncoded(input: ArrayBuffer) {
const bytes = new Uint8Array(input);
return urlEncodeBase64(window.btoa(String.fromCharCode(...bytes)));
}
urlEncodeBase64(input: string) {
return input.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/\=/g, '');
}
- 工夫が必要なのは以下の機能である。すべてブラウザ搭載である
window
およびwindow.crypto
に紐づく関数を利用して実装できていることが確認できる- ランダムな文字列の生成
- BASE64のエンコード
- sha256によるハッシュ化
- トークン取得のPOST時に、暗号化するまえの元の文字列である
code_verifier
が必要になる- こちらはメモリ上に持つ形として、ブラウザ内のストレージには保管しないこととした。破棄のタイミングの検討が必要になるため。加えて、この機能を開発したアプリケーション上、保持したcode_verifierを破棄するようなユーザー操作は想定されない。すぐにPOSTする操作が予想されるため、メモリ上に保持しておくことでも大きな問題は無いと判断している
- BASE64でエンコードするために、作った
string
を一度ArrayBuffer
に戻す必要がある