前提
"LINE Developers"や"LINE Official Management Account"などへの登録は事前に済ませているものとする
LINEのコールバックURIの登録や、アカウントの作成は済んでいるものとする。
背景
LINEログインの認証にPKCEが対応された
→ 既存のログインにPKCE対応を行うことにしよう!
PKCEとは
PKCEとはProof Key for Code Exchangeの略でRFC7636として公開された仕様
認可コード横取り攻撃への対応策として使われる
PKCEの仕様
- "code_verifier"と"code_challenge"という二つの値を使う
- "code_verifier"から"code_challenge"を生成する生成器を使用する
- 生成器は"code_verifier"をSHA256でハッシュ化し、Base64URL形式にエンコードした値を出力する
- 同じ"code_verifier"から同じ"code_challenge"を生成することができるが、逆は生成不可である
認可コード横取り攻撃、PKCE未実装時
認可コード横取り攻撃、LINE PKCE実装時
LINEログインのイメージと対応箇所
実装
- 今回の実装は、code_verifierとcode_challengeの生成、認可後のアクセストークンの送信はrustで行う
- code_verifierとcode_challengeの生成は'pkce-Rust'というCrateを使用する
- code_verifierがバイナリーであるため、LINEログインで使用するためにcharに変換を行う
code verifierの生成(クレートから引用)
pkce-rs
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789-.~_";
pub fn code_verifier(length: usize) -> Vec<u8> {
let mut rng = thread_rng();
(0..length)
.map(|_| {
let i = rng.gen_range(0, CHARS.len());
CHARS[i]
})
.collect()
}
code challengeの生成(クレートから引用)
pkce-rs
pub fn code_challenge() {
// ハッシュ化
let mut sha = Sha256::new();
sha.update(code_verifier);
let result = sha.finalize();
// base64でエンコード
base64_url_encode(&result[..])
}
実際にLINEログインに使用する値
generate_verifier_and_challenge
fn generate_verifier_and_challenge() -> (String, String) {
// 文字数の最小値と最大値(LINEの仕様)
let char_size_min = 43;
let char_size_max = 128 + 1;
let rng = &mut thread_rng();
let size = rng.gen_range(char_size_min..char_size_max);
// code_verifierとcode_challengeの生成
let u8_verify = pkce::code_verifier(size);
let code_verifier = u8_verify.iter().map(|&s| s as char).collect::<String>();
let code_challenge = pkce::code_challenge(&u8_verify);
(code_verifier, code_challenge)
}
LINEログインの認可URIにcode_challengeを組み込む
make_redirect_uri
fn make_redirect_uri() {
let pkce = generate_verifier_and_challenge();
let redirect_uri = format!("https://access.line.me/oauth2/v2.1/authorize?scope=profile&prompt=consent&bot_prompt=normal&response_type=code&client_id=xxxxx&redirect_uri=https://ussy.index.html&state=xxxx&code_challenge={}&code_challenge_method=S256", pkce.1);
}
認可URI
https://access.line.me/oauth2/v2.1/authorize?scope=profile&prompt=consent&bot_prompt=normal&response_type=code&client_id=xxxxx&redirect_uri=https://ussy.index.html&state=xxxx&code_challenge=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&code_challenge_method=S256
LINEログインのOauth認証の仕様
- 認可URIの最後にcode_challengeとcode_challenge_methodを追加することでPKCE対応を行うことができる
- code_challengeがついていないURIについては、通常のPKCE対応を行っていない認可URIとみなされる(LINE Developers)
アクセストークンの発行
- 認可URIにcode_challengeを付与したので、アクセストークンのリクエストにcode_verifierの情報を付与する
- code_verifierは例えばDBなどに保存する
- 今回はwebアプリの想定し、SessionStorageを用いてcode_verifierを保存した
- 保存したcode_verifierをRust側に渡し、アクセストークンのリクエストを作成する
アクセストークンの発行
request_bodyの送信部分
let request_body: Vec<(&str, &str)> = vec![
("grant_type", "authorization_code"),
("code", code),
("redirect_uri", redirect_uri),
("client_id", client_id),
("client_secret", client_secret),
("code_verifier", code_verifier),
];
let client = reqwest::Client::new();
let res = client
.post(
"https://api.line.me/v2/oauth/accessToken"
)
.form(¶ms)
.send()
.await?;
まとめ
-
LINEログインのPKCE対応で以下を行った
- Rustでcode_verifierとcode_challengeの生成部分の実装
- redirect_uriとaccess_token_request作成部分の実装
他のサービスでも、Oauth2.0拡張のPKCEに対応しているものであれば、そのサービスの仕様に基づいて同様に実装できる