OAuth触るの5年ぶり。ナナオです。
今回はLINEのOAuth認証を、実際にサーバーを実装しながら試していきたいと思います。
LINEログイン v2.1 APIリファレンス | LINE Developers
そもそもOAuthとは?といったところは↓
一番分かりやすい OAuth の説明 #OAuth - Qiita
OAuthでログイン画面表示まで まずLINE Developersコンソールからログインチャネルを作成します。
これはMessaging APIなどとは別で作成する必要があります。
以下のように設定しました。
続けて、「LINEログイン設定」からコールバックURLを設定します。
コールバックURLのリクエスト先はまだ実装していなくても大丈夫です。
今回はウェブアプリから使用する設定で実装します。
ここまで出来たらブラウザからログインの様子を確認することができます。
認可リクエストのドキュメントを確認し、URLを作ってブラウザからリクエストしてみましょう。
うまくいけば以下のようなLINEのログイン画面に遷移します。
コールバックURLを実装する 今のままだとリダイレクトURLの先には何もないので、自分で実装しましょう。
Cargoパッケージを作成します。
cargo new rust-line-oauth-playground 必要な依存関係も追加しておきます。
[dependencies] axum = "0.7.1" dotenv = "0.15.0" reqwest = { version = "0.11.22", features = ["json"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" tokio = { version = "1.34.0", features = ["macros", "rt", "rt-multi-thread"] } では実際にリダイレクト先の実装をしてあげましょう。
これを実施すると、アクセストークンが取得できるようにになります。
以下のように行いました。
use axum::{extract::{Query, State}, routing::get, Router}; use serde::Deserialize; use reqwest::Client; use serde_json::{Value, json}; #[derive(Debug, Deserialize)] struct LineCallbackQuery { code: String, state: String, friendship_status_changed: Option<bool>, liffClientId: Option<String>, liffRedirectUri: Option<String>, } #[derive(Debug, Deserialize)] struct LineCallbackError { error: String, error_description: Option<String>, state: Option<String>, } #[derive(Debug, Deserialize)] struct AccessTokenResponse { access_token: String, expires_in: i32, id_token: String, refresh_token: String, scope: String, token_type: String, } #[derive(Debug, Clone)] struct BaseSetting { redirect_uri: String, client_id: String, client_secret: String, } #[derive(Debug, Clone)] struct AppState { http_client: Client, setting: BaseSetting, } async fn line_callback( Query(query): Query<Value>, State(state): State<AppState>, ) { // エラーの場合 // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#receiving-an-error-response if query.get("error").is_some() { let query: LineCallbackError = serde_json::from_value(query).unwrap(); eprintln!("リダイレクト時にエラーが発生しました: {:?}", query); return } // 正常な場合 // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#receiving-the-authorization-code let query: LineCallbackQuery = serde_json::from_value(query).unwrap(); let params = json!({ "grant_type": "authorization_code", "code": query.code, "redirect_uri": state.setting.redirect_uri, "client_id": state.setting.client_id, "client_secret": state.setting.client_secret, }); // アクセストークンを取得する // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#get-access-token let res = state.http_client.post("https://api.line.me/oauth2/v2.1/token") .form(¶ms) .send().await.unwrap(); let res_json: AccessTokenResponse = res.json().await.unwrap(); println!("res: {:?}", res_json); } #[tokio::main] async fn main() { // 環境変数をdotenvで取得する dotenv::dotenv().ok(); let setting = BaseSetting { redirect_uri: std::env::var("REDIRECT_URI") .expect("REDIRECT_URI must be set"), client_id: std::env::var("CLIENT_ID") .expect("CLIENT_ID must be set"), client_secret: std::env::var("CLIENT_SECRET") .expect("CLIENT_SECRET must be set"), }; let http_client = reqwest::Client::new(); let state = AppState { http_client, setting, }; let app = Router::new() .route("/callback", get(line_callback)) .with_state(state); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); println!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); } あとはルートパッケージ直下に.envを配置します。
...