UiPath Orchestrator APIを非機密アプリケーションで使用するときの code_challenge 生成方法

こんにちは。[UiPath Friendsコミュニティ](https://uipath-friends.doorkeeper.jp/)運営メンバの @masatomix です。
この記事はUiPathブログ発信チャレンジ2022サマーの5日目の記事のふたつめです。
ふたつめの記事はめっちゃ備忘でゴメンナサイ。

https://note.com/shumpei_w/n/n19383ce5abb1

昨日は [shuy](https://twitter.com/shuy_oooo)さんの記事、明日は[よしやんやん](https://twitter.com/myoshidan)さんの記事です。

https://qiita.com/shuy/items/c92affc3d7b2433dba89




## イントロ

[About authorizing external applications](https://docs.uipath.com/automation-cloud/lang-ja/docs/about-external-applications) にも説明がありますが、Orchestrator APIを利用するためにアクセストークンを取得するさい、Client Secretを保存できないような非機密な環境の場合は、**Client Secretを発行しない方式でアクセストークンを取得**する必要があります。

[外部アプリケーションを使用して UiPath のリソースにアクセスする](https://docs.uipath.com/automation-cloud/lang-ja/docs/setting-up-the-external-application#authorization-code-with-pcke)  にもあるとおり **「PKCE による認可コードフロー」を使ってね** ってことですね。
今回はそこで出てくる``code_verifier/code_challenge`` についての備忘です[^1]。

[^1]: めっちゃニッチな話題なので、、誰も興味がなさそうだけどかまわずいきます



## code_challenge の生成方法

認可エンドポイントのパラメタに``code_challenge``というのがあるのですが、その値はトークンエンドポイントにアクセスしたときにパラメタで渡す``code_verifier``から (Sha256でハッシュすることで[^2])生成されています。

その``code_challenge``の算出方法が上記のUiPathのサイトに書いてなかったので、JavaScriptで値を生成するサンプルを書いたので載っけておきます。

[^2]: ```code_challenge_method``` がS256前提で話を進めます



## 環境

Macですが、JavaScriptが動けばたぶんなんでもOK。

```
% sw_vers
ProductName:	macOS
ProductVersion:	12.4   <- macOS Monterey
BuildVersion:	21F79

% node --version
v16.14.0

% npm -v
8.3.1
%
```

## やってみる

node.js のプロジェクトファイル作成

```
% mkdir tmp && cd $_
% cat >> package.json << EOF
{
  "name": "type",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Masatomi KINO <masatomix@ki-no.org> (http://qiita.com/masatomix)",
  "license": "ISC",
  "dependencies": {
    "ts-node": "^10.8.1",
    "typescript": "^4.7.4"
  }
}
EOF
%
```

( 前までは "crypto": "^1.0.1", が必要だったけど、いまは不要っぽい)


ソースコードは以下

```javascript
% mkdir src
% cat >> src/index.ts << EOF
import crypto from 'crypto'

const sha256 = (target: string)=> {
  const base64 = crypto
    .createHash('sha256')
    .update(target, 'utf8')
    .digest('base64')
  return base64
    .replace('+', '-')
    .replace('/', '_')
    .replace('=', '')
}

// 以下実行例:
const main = () => {
  const code_verifier:string = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
  const code_challenge:string = sha256(code_verifier)

  console.log('code_verifier: '+ code_verifier)
  console.log('code_challenge: '+ code_challenge)
  // expected: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

  // https://datatracker.ietf.org/doc/html/rfc7636#appendix-B
};

main()
EOF
%

```

動かしてみます。

```
%  npm i 
%  npx ts-node src/index.ts
code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
%
```

なんだか ``code_verifier``から``code_challenge``が生成できましたね。ところでコレあってるの？って気になると思いますが、上記の ``code_verifier/code_challenge`` の値は、[PKCEの仕様書(RFC7636)](https://datatracker.ietf.org/doc/html/rfc7636#appendix-B) にあるサンプルを検証したものです :-)
仕様書のサンプル通りの値が出力できてるのでOKぽいですね。



## 最後に

さあここまでやったけど、ワンライナーを上げてる人を見つけたので、載っけておきます。感謝です:-)


```
% echo -n 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk' \
  | shasum -a 256  | xxd -r -p | base64 \
  | sed -e 's/=//g' | sed -e 's/+/-/g' | sed -e 's/\//_/g'
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
```
(sha256でハッシュして、16進に変換、Base64 encodeして、sedで「=」→「」, 「+」→「-」,「/」→「_」へ置換してる)

参考: https://qiita.com/nannany_hey/items/b30ac759553661dfb265



以上、お疲れさまでした！