LoginSignup
3
1

More than 1 year has passed since last update.

RustでLINEログイン認証のPKCE対応をした話

Last updated at Posted at 2021-08-12
1 / 18

前提

  • "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未実装時

未実装.png


認可コード横取り攻撃、LINE PKCE実装時

実装時.png


LINEログインのイメージと対応箇所

PKCE対応箇所.png


実装

  • 今回の実装は、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(&params)
        .send()
        .await?;

まとめ

  • LINEログインのPKCE対応で以下を行った

    • Rustでcode_verifierとcode_challengeの生成部分の実装
    • redirect_uriとaccess_token_request作成部分の実装
  • 他のサービスでも、Oauth2.0拡張のPKCEに対応しているものであれば、そのサービスの仕様に基づいて同様に実装できる


参考資料

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1