Actix-webのFromRequestで認証処理を共通化する


注意:不正確な内容が含まれている可能性があるため、下記の参考 URL をまずは確認頂くのが良いと思います。

Actix Web/Rust API: Authorization Code Sample

auth0-developer-hub / api_actix-web_rust_hello-world

認証フローを共通化したい

Rust の Actix_web フレームワークを利用している API サーバーで、トークン認証をかけたいルート下記のようなコードを書いていました。アクセスする側は Authorization Header に JWT トークンを付与してアクセス、API 側はトークンを検証して認証するタイプです。

イメージ

  let server = HttpServer::new(move || {
      App::new()
          .wrap(TracingLogger::default())
          .route("/book", web::post().to(add_book))
pub async fn add_book(
    req: HttpRequest,
    pool: web::Data<PgPool>,
    form: web::Json<FormData>,
) -> HttpResponse {
    // request headerからtokenを取得する処理
    let jwt = match get_token(&req) {
        Ok(v) => v,
        _ => {
           // 認証Errの処理
        }
    };
    // tokenを検証しOKなら処理を進める
    match validate_token(&jwt) {
        Ok(v) => {
          // 認証OKの処理
        }
        Err(_) => {
          // 認証Errの処理
        }
    }
   // 認証Errの処理
}

トークンを受け取る、トークンを検証する処理は認証用ルートでは同じものを使いまわしたいが方法が良くわからず、調べていると下記の記事やコード例で FromRequest を利用していたため真似しました。

Actix Web/Rust API: Authorization Code Sample

auth0-developer-hub / api_actix-web_rust_hello-world

actix_web の FromRequest を実装した struct を作成する

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub exp: i64,
    _permissions: Option<HashSet<String>>,
}

impl FromRequest for Claims {
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
        let request = req.clone();
       Box::pin(async move {
           let jwt = match get_token(&request) {
               Ok(v) => v,
               _ => {
                   return Err(ErrorUnauthorized("this route is protected"))
               }
           };
           match validate_jwt_token(&jwt) {
               Ok(v) => {
                   Ok(v)
               },
               // ValidationErrorは独自に実装したエラー
               Err(ValidationError::TokenExpired) => Err(ErrorUnauthorized("The access token expired.")),
               Err(ValidationError::InvalidToken) => Err(ErrorUnauthorized("The access token invalid.")),
               _ => Err(ErrorInternalServerError("internal server error."))
           }
       })
    }
}

認証ルートで行っていた、トークンの取得、検証処理を from_request で行います。トークン認証が失敗したら 401 か 500 が返却されて処理が進みません。

元の認証ルートに Claims を利用する

元々の処理からトークン取得、検証の処理を外し、引数に Claims を受け取ればトークン検証が行われます。

pub async fn add_book(
    _claims: Claims,
    // req: HttpRequest,
    pool: web::Data<PgPool>,
    form: web::Json<FormData>,
) -> HttpResponse {
   // トークン検証OKの時の処理の書く
}

コード例を真似して書いていたとき、Actix のバージョン違いなのもあって割と詰まりました。