Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

サーバサイド・クライアントサイドに応じたトークン検証方法を考慮 #41

Open
riii111 opened this issue Oct 8, 2024 · 2 comments
Labels
backend bug Something isn't working frontend

Comments

@riii111
Copy link
Owner

riii111 commented Oct 8, 2024

サーバサイドでAPI呼ぶならAuthorizationヘッダで検証できなさそう。
(Cookieにはセキュリティの都合でHttpOnly属性付与している)
フロントとバックエンド両方調べる。

@riii111 riii111 added bug Something isn't working frontend backend labels Oct 8, 2024
@riii111
Copy link
Owner Author

riii111 commented Oct 8, 2024

フロントエンドとバックエンドの認証エラーの問題について、詳細な調査を行った結果、以下の点が判明しました:

  • フロントエンド側: クライアントサイドのJavaScriptからaccess_tokenを読み取ろうとしましたが、HttpOnly属性が設定されているため、Cookies.get("access_token")undefinedを返しています。
  • バックエンド側: クッキーは正常に受信されていますが、サーバーはAuthorizationヘッダーを期待しており、Authorizationヘッダーにトークンが含まれていないため、認証エラー(401)が発生しています。
  • cURLテスト: Authorizationヘッダーを手動で設定した場合、認証が成功しています。

これらの状況から、access_tokenHttpOnlyクッキーとして設定されており、クライアントサイドからはアクセスできないため、Authorizationヘッダーを設定できず、結果としてサーバー側で認証に失敗していることがわかります。

解決策

1. サーバー側でクッキーからJWTトークンを取得して認証するように変更

HttpOnlyクッキーを利用してセキュアにトークンを管理している場合、クライアントサイドでAuthorizationヘッダーを設定するのではなく、サーバー側でクッキーからトークンを直接取得して認証を行う方法が推奨されます。

a. カスタムミドルウェアの作成

actix_webでは、カスタムミドルウェアを作成してクッキーからトークンを取得し、認証を行うことができます。以下にその実装例を示します。

// backend/src/middleware/jwt_from_cookie.rs

use actix_web::{dev::ServiceRequest, Error, HttpMessage};
use actix_service::{Service, Transform};
use futures::future::{ok, Ready, LocalBoxFuture};
use crate::usecases::auth::AuthUseCase;
use crate::errors::AppError;
use std::sync::Arc;
use log::{debug, error};

pub struct JwtFromCookie {
    auth_usecase: Arc<AuthUseCase>,
}

impl JwtFromCookie {
    pub fn new(auth_usecase: Arc<AuthUseCase>) -> Self {
        Self { auth_usecase }
    }
}

impl<S, B> Transform<S, ServiceRequest> for JwtFromCookie
where
    S: Service<ServiceRequest, Response = actix_web::dev::ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = actix_web::dev::ServiceResponse<B>;
    type Error = Error;
    type Transform = JwtFromCookieMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(JwtFromCookieMiddleware {
            service: Arc::new(service),
            auth_usecase: self.auth_usecase.clone(),
        })
    }
}

pub struct JwtFromCookieMiddleware<S> {
    service: Arc<S>,
    auth_usecase: Arc<AuthUseCase>,
}

impl<S, B> Service<ServiceRequest> for JwtFromCookieMiddleware<S>
where
    S: Service<ServiceRequest, Response = actix_web::dev::ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = actix_web::dev::ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(
        &self,
        ctx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.service.poll_ready(ctx)
    }

    fn call(&self, mut req: ServiceRequest) -> Self::Future {
        let auth_usecase = self.auth_usecase.clone();
        let srv = self.service.clone();

        Box::pin(async move {
            // クッキーからアクセス トークンを取得
            if let Some(cookie) = req.cookie("access_token") {
                let token = cookie.value();
                debug!("Validating JWT token from cookie: {}", token);

                match auth_usecase.verify_access_token(token).await {
                    Ok(_) => {
                        debug!("Token validation succeeded");
                        srv.call(req).await
                    },
                    Err(e) => {
                        error!("Token validation failed: {:?}", e);
                        // 認証失敗時のレスポンス
                        Err(actix_web::error::ErrorUnauthorized("Invalid token"))
                    }
                }
            } else {
                debug!("No access_token cookie found");
                // トークンがない場合のレスポンス
                Err(actix_web::error::ErrorUnauthorized("No token provided"))
            }
        })
    }
}

b. ミドルウェアの登録

作成したカスタムミドルウェアをmain.rsで登録します。

// backend/src/main.rs

use actix_web::{App, HttpServer};
use crate::middleware::jwt_from_cookie::JwtFromCookie;
use std::sync::Arc;
use crate::usecases::auth::AuthUseCase;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // AuthUseCaseの初期化(必要に応じて)
    let auth_usecase = Arc::new(AuthUseCase::new(/* 引数 */));

    HttpServer::new(move || {
        App::new()
            .wrap(JwtFromCookie::new(auth_usecase.clone()))
            // 他のミドルウェアやルートの設定
            .service(/* APIルート */)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

2. フロントエンド側のcustomFetch関数の修正

サーバー側でクッキーからトークンを取得し認証を行うように変更したため、クライアント側でAuthorizationヘッダーを設定する必要はありません。そのため、customFetch関数からAuthorizationヘッダーの設定部分を削除します。

修正前: frontend/lib/api/core.ts

// ...省略...

const authHeader = await getAuthHeader();
console.log("authHeader", authHeader);

// ヘッダーをオブジェクトとして構築
const headers: Record<string, string> = {
  ...optionHeaders,
  ...authHeader,
};

if (!headers["Content-Type"]) {
  headers["Content-Type"] = "application/json";
}

console.log("Request Headers:", headers);

const fetchOptions: RequestInit = {
  method,
  headers,
  credentials: "include",
  cache,
  mode: "cors",
};

修正後: frontend/lib/api/core.ts

// frontend/lib/api/core.ts

import { toast } from "@/lib/hooks/use-toast";
import { ApiResponse } from "@/types/api";
// import { getClientSideAuthHeader } from "@/lib/utils/cookies"; // 不要になったためコメントアウト

const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;

export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

// GETメソッドの場合はクエリパラメータ、それ以外はリクエストボディの型を定義
type FetchParams<T, M extends HttpMethod> = M extends "GET"
  ? T
  : Record<string, string | number | boolean>;
type FetchBody<T, M extends HttpMethod> = M extends
  | "POST"
  | "PUT"
  | "DELETE"
  | "PATCH"
  ? T | FormData
  : never;

// フェッチオプションのインターフェース定義
interface IFetchOptions<T extends Record<string, any>, M extends HttpMethod> {
  headers?: Record<string, any>;
  method: M;
  params?: FetchParams<T, M>;
  body?: FetchBody<T, M>;
  cache?: RequestCache;
}

// APIエラーを表すカスタムエラークラス
class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message);
  }
}

// カスタムフェッチ関数
export async function customFetch<
  M extends HttpMethod,
  RequestInput extends Record<string, any> = Record<string, any>,
  RequestResult = unknown
>(
  endpoint: string,
  {
    headers: optionHeaders,
    method,
    body,
    params,
    cache = "no-cache",
  }: IFetchOptions<RequestInput, M>
): Promise<ApiResponse<RequestResult>> {
  if (!API_BASE_URL) {
    throw new Error("API_BASE_URL is not defined");
  }

  // 末尾にスラッシュ追加する事でプロキシ・バックエンドなどのサーバー側のリダイレクトを回避し、パフォーマンスを僅かに向上させる
  let url = `${API_BASE_URL}${endpoint}${endpoint.endsWith("/") ? "" : "/"}`;

  // ヘッダーをオブジェクトとして構築
  const headers: Record<string, string> = {
    ...optionHeaders,
    // Authorizationヘッダーの設定を削除
  };

  if (!headers["Content-Type"]) {
    headers["Content-Type"] = "application/json";
  }

  console.log("Request Headers:", headers); // 確認用ログ

  const fetchOptions: RequestInit = {
    method,
    headers,
    credentials: "include", // クッキーを含める
    cache,
    mode: "cors",
  };

  if (body) {
    fetchOptions.body = body instanceof FormData ? body : JSON.stringify(body);
  }

  // GETメソッドの場合、クエリパラメータをURLに追加
  if (params && method === "GET") {
    const searchParams = new URLSearchParams(params as Record<string, string>);
    url += `?${searchParams.toString()}`;
  }

  try {
    let response = await fetch(url, fetchOptions);

    // TODO: 401エラーの場合、アクセストークンを更新して再リクエスト

    if (!response.ok) {
      let errorMessage = "エラーが発生しました";
      try {
        const errorData = await response.json();
        errorMessage = errorData.message || errorMessage;
      } catch (e) {
        // JSON解析に失敗した場合は、デフォルトのエラーメッセージを使用
      }
      throw new ApiError(response.status, errorMessage);
    }

    const data: ApiResponse<RequestResult> = await response.json();
    return data;
  } catch (error) {
    if (error instanceof ApiError) {
      if (typeof window !== "undefined") {
        // クライアントサイドの場合のみtoastを使用
        toast({
          variant: "destructive",
          title: "エラー",
          description: error.message,
        });
      } else {
        // サーバーサイドの場合はコンソールにエラーを出力
        console.error("API Error:", error.message);
      }
    } else {
      if (typeof window !== "undefined") {
        // クライアントサイドの場合のみtoastを使用
        toast({
          variant: "destructive",
          title: "エラー",
          description: "予期せぬエラーが発生しました。",
        });
      } else {
        // サーバーサイドの場合はコンソールにエラーを出力
        console.error("Unexpected Error:", error);
      }
    }
    throw error;
  }
}

変更点:

  1. Authorizationヘッダーの設定を削除: クッキーからトークンを取得するため、Authorizationヘッダーを設定する必要がなくなりました。
  2. credentials: "include"の維持: クッキーを自動的にリクエストに含めるために設定を維持しています。

3. バックエンドのミドルウェア修正

クッキーからトークンを取得するようにミドルウェアを修正する必要があります。以下は、actix_webでカスタムミドルウェアを作成し、クッキーからaccess_tokenを取得して認証を行う例です。

a. カスタムミドルウェアの実装

// backend/src/middleware/jwt_from_cookie.rs

use actix_web::{dev::ServiceRequest, Error, HttpMessage};
use actix_service::{Service, Transform};
use futures::future::{ok, Ready, LocalBoxFuture};
use crate::usecases::auth::AuthUseCase;
use crate::errors::AppError;
use std::sync::Arc;
use log::{debug, error};

pub struct JwtFromCookie {
    auth_usecase: Arc<AuthUseCase>,
}

impl JwtFromCookie {
    pub fn new(auth_usecase: Arc<AuthUseCase>) -> Self {
        Self { auth_usecase }
    }
}

impl<S, B> Transform<S, ServiceRequest> for JwtFromCookie
where
    S: Service<ServiceRequest, Response = actix_web::dev::ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = actix_web::dev::ServiceResponse<B>;
    type Error = Error;
    type Transform = JwtFromCookieMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(JwtFromCookieMiddleware {
            service: Arc::new(service),
            auth_usecase: self.auth_usecase.clone(),
        })
    }
}

pub struct JwtFromCookieMiddleware<S> {
    service: Arc<S>,
    auth_usecase: Arc<AuthUseCase>,
}

impl<S, B> Service<ServiceRequest> for JwtFromCookieMiddleware<S>
where
    S: Service<ServiceRequest, Response = actix_web::dev::ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = actix_web::dev::ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(
        &self,
        ctx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.service.poll_ready(ctx)
    }

    fn call(&self, mut req: ServiceRequest) -> Self::Future {
        let auth_usecase = self.auth_usecase.clone();
        let srv = self.service.clone();

        Box::pin(async move {
            // クッキーからトークンを取得
            if let Some(cookie) = req.cookie("access_token") {
                let token = cookie.value();
                debug!("Validating JWT token from cookie: {}", token);

                match auth_usecase.verify_access_token(token).await {
                    Ok(_) => {
                        debug!("Token validation succeeded");
                        srv.call(req).await
                    },
                    Err(e) => {
                        error!("Token validation failed: {:?}", e);
                        // 認証失敗時のレスポンス
                        Err(actix_web::error::ErrorUnauthorized("Invalid token"))
                    }
                }
            } else {
                debug!("No access_token cookie found");
                // トークンがない場合のレスポンス
                Err(actix_web::error::ErrorUnauthorized("No token provided"))
            }
        })
    }
}

b. ミドルウェアの登録

作成したカスタムミドルウェアをmain.rsで登録します。

// backend/src/main.rs

use actix_web::{App, HttpServer};
use crate::middleware::jwt_from_cookie::JwtFromCookie;
use std::sync::Arc;
use crate::usecases::auth::AuthUseCase;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // AuthUseCaseの初期化(必要に応じて)
    let auth_usecase = Arc::new(AuthUseCase::new(/* 引数 */));

    HttpServer::new(move || {
        App::new()
            .wrap(JwtFromCookie::new(auth_usecase.clone()))
            // 他のミドルウェアやルートの設定
            .service(/* APIルート */)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

4. 全体のフロー確認

以下のフローで認証が行われるように設定します:

  1. ユーザーがフロントエンドからリクエストを送信:

    • customFetch関数はAuthorizationヘッダーを設定せず、クッキーを自動的にリクエストに含めます(credentials: "include")。
  2. サーバーがリクエストを受信:

    • カスタムミドルウェアJwtFromCookieがクッキーからaccess_tokenを取得し、JWTの検証を行います。
    • トークンが有効であれば、リクエストを次のハンドラに渡します。
    • トークンが無効または存在しない場合、401エラーを返します。

5. トークンの発行とクッキー設定の確認

サーバー側でトークンを発行する際に、access_tokenHttpOnlyクッキーとして設定していることを確認します。例:

// backend/src/endpoints/auth.rs

use actix_web::{HttpResponse, Responder};
use actix_web::http::header::COOKIE;
use actix_web::cookie::Cookie;

async fn login(...) -> impl Responder {
    // ユーザー認証ロジック...

    // JWTトークンの生成
    let token = generate_jwt_token(...);

    // クッキーとして設定
    let access_cookie = Cookie::build("access_token", token)
        .path("/")
        .http_only(true)
        .secure(true) // HTTPSの場合はtrueに設定
        .same_site(actix_web::cookie::SameSite::Lax)
        .finish();

    HttpResponse::Ok()
        .cookie(access_cookie)
        .json(/* レスポンス */)
}

6. その他の確認事項

  • CORS設定:

    • サーバー側でCORSを適切に設定し、credentialsの送受信を許可します。
    use actix_cors::Cors;
    
    HttpServer::new(move || {
        App::new()
            .wrap(Cors::default()
                .allow_any_origin()
                .allowed_methods(vec!["GET", "POST", "PUT", "DELETE", "PATCH"])
                .allowed_headers(vec![actix_web::http::header::AUTHORIZATION, actix_web::http::header::CONTENT_TYPE])
                .supports_credentials()
            )
            .wrap(JwtFromCookie::new(auth_usecase.clone()))
            // 他のミドルウェアやルートの設定
            .service(/* APIルート */)
    })
  • クッキーの属性:

    • Secure属性を適切に設定(開発環境ではfalse、本番環境ではtrue)。
    • SameSite属性を適切に設定。
  • トークンの有効期限:

    • トークンが適切な有効期限を持っていることを確認。

7. フロントエンドの確認

customFetch関数がAuthorizationヘッダーを設定せずに、クッキーを自動的に送信するようになっていることを確認します。これにより、サーバー側でクッキーからトークンを取得して認証が行われます。

修正後のcustomFetch関数の例:

// frontend/lib/api/core.ts

import { toast } from "@/lib/hooks/use-toast";
import { ApiResponse } from "@/types/api";

const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;

export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

// GETメソッドの場合はクエリパラメータ、それ以外はリクエストボディの型を定義
type FetchParams<T, M extends HttpMethod> = M extends "GET"
  ? T
  : Record<string, string | number | boolean>;
type FetchBody<T, M extends HttpMethod> = M extends
  | "POST"
  | "PUT"
  | "DELETE"
  | "PATCH"
  ? T | FormData
  : never;

// フェッチオプションのインターフェース定義
interface IFetchOptions<T extends Record<string, any>, M extends HttpMethod> {
  headers?: Record<string, any>;
  method: M;
  params?: FetchParams<T, M>;
  body?: FetchBody<T, M>;
  cache?: RequestCache;
}

// APIエラーを表すカスタムエラークラス
class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message);
  }
}

// カスタムフェッチ関数
export async function customFetch<
  M extends HttpMethod,
  RequestInput extends Record<string, any> = Record<string, any>,
  RequestResult = unknown
>(
  endpoint: string,
  {
    headers: optionHeaders,
    method,
    body,
    params,
    cache = "no-cache",
  }: IFetchOptions<RequestInput, M>
): Promise<ApiResponse<RequestResult>> {
  if (!API_BASE_URL) {
    throw new Error("API_BASE_URL is not defined");
  }

  // 末尾にスラッシュ追加する事でプロキシ・バックエンドなどのサーバー側のリダイレクトを回避し、パフォーマンスを僅かに向上させる
  let url = `${API_BASE_URL}${endpoint}${endpoint.endsWith("/") ? "" : "/"}`;

  // ヘッダーをオブジェクトとして構築
  const headers: Record<string, string> = {
    ...optionHeaders,
    // Authorizationヘッダーの設定を削除
  };

  if (!headers["Content-Type"]) {
    headers["Content-Type"] = "application/json";
  }

  console.log("Request Headers:", headers); // 確認用ログ

  const fetchOptions: RequestInit = {
    method,
    headers,
    credentials: "include", // クッキーを自動的に含める
    cache,
    mode: "cors",
  };

  if (body) {
    fetchOptions.body = body instanceof FormData ? body : JSON.stringify(body);
  }

  // GETメソッドの場合、クエリパラメータをURLに追加
  if (params && method === "GET") {
    const searchParams = new URLSearchParams(params as Record<string, string>);
    url += `?${searchParams.toString()}`;
  }

  try {
    let response = await fetch(url, fetchOptions);

    // TODO: 401エラーの場合、アクセストークンを更新して再リクエスト

    if (!response.ok) {
      let errorMessage = "エラーが発生しました";
      try {
        const errorData = await response.json();
        errorMessage = errorData.message || errorMessage;
      } catch (e) {
        // JSON解析に失敗した場合は、デフォルトのエラーメッセージを使用
      }
      throw new ApiError(response.status, errorMessage);
    }

    const data: ApiResponse<RequestResult> = await response.json();
    return data;
  } catch (error) {
    if (error instanceof ApiError) {
      if (typeof window !== "undefined") {
        // クライアントサイドの場合のみtoastを使用
        toast({
          variant: "destructive",
          title: "エラー",
          description: error.message,
        });
      } else {
        // サーバーサイドの場合はコンソールにエラーを出力
        console.error("API Error:", error.message);
      }
    } else {
      if (typeof window !== "undefined") {
        // クライアントサイドの場合のみtoastを使用
        toast({
          variant: "destructive",
          title: "エラー",
          description: "予期せぬエラーが発生しました。",
        });
      } else {
        // サーバーサイドの場合はコンソールにエラーを出力
        console.error("Unexpected Error:", error);
      }
    }
    throw error;
  }
}

ポイント:

  • Authorizationヘッダーの削除: トークンはクッキー経由で送信されるため、Authorizationヘッダーを設定する必要がなくなります。
  • credentials: "include"の設定: クッキーを自動的にリクエストに含めるための設定です。

8. クッキーの設定確認

サーバー側でaccess_tokenクッキーを設定する際に、以下の点を確認してください:

  • HttpOnly属性: セキュリティ向上のためにHttpOnly属性を設定します(クライアント側からのJavaScriptアクセスを防ぐ)。
  • Secure属性: 本番環境ではSecure属性を設定し、HTTPS経由のみクッキーが送信されるようにします。
  • SameSite属性: StrictまたはLaxを設定し、クロスサイトリクエストを制限します。

例: クッキーの設定

use actix_web::{HttpResponse, Responder};
use actix_web::cookie::Cookie;

async fn login(...) -> impl Responder {
    // ユーザー認証ロジック...

    // JWTトークンの生成
    let token = generate_jwt_token(...);

    // クッキーとして設定
    let access_cookie = Cookie::build("access_token", token)
        .path("/")
        .http_only(true)
        .secure(false) // 開発環境ではfalse、本番ではtrueに設定
        .same_site(actix_web::cookie::SameSite::Lax)
        .finish();

    HttpResponse::Ok()
        .cookie(access_cookie)
        .json(/* レスポンス */)
}

9. 結果の確認

  1. ユーザーがログインすると、access_tokenHttpOnlyクッキーとして設定される。
  2. フロントエンドからのAPIリクエストは、クッキーを自動的に含める。
  3. サーバー側のカスタムミドルウェアがクッキーからaccess_tokenを取得し、認証を行う。
  4. 認証が成功すると、APIリクエストが正常に処理される。

10. 追加のデバッグ方法

  • サーバー側のログ確認:

    • カスタムミドルウェア内でトークンの取得および検証のログを追加し、認証プロセスが正しく行われているか確認します。
    debug!("Access token from cookie: {}", token);
  • ブラウザの開発者ツール確認:

    • 「Network」タブ: APIリクエストがクッキーを含んで送信されているか確認。
    • 「Application」タブ: クッキーが正しく設定されているか確認。
  • PostmanやInsomniaでのテスト:

    • クッキーを含めたリクエストを手動で送信し、サーバー側の認証が正常に行われるか確認。

まとめ

現在の認証エラーの原因は、フロントエンドでaccess_tokenクッキーを読み取れず、Authorizationヘッダーを設定できないことにあります。これを解決するために、以下のステップを実行しました:

  1. サーバー側でクッキーからJWTトークンを取得して認証するカスタムミドルウェアを作成。
  2. クライアント側のcustomFetch関数からAuthorizationヘッダーの設定を削除し、クッキーを自動で含めるように変更。
  3. サーバー側のミドルウェアを登録し、クッキーからトークンを取得して認証を行うように設定。
  4. クッキーの属性(HttpOnlySecureSameSite)を適切に設定。

これにより、セキュアにトークンを管理しつつ、認証エラーを解消できるはずです。問題が解決しない場合や追加のエラーが発生した場合は、詳細なログや具体的なエラーメッセージを共有いただければ、さらにサポートいたします。

@riii111
Copy link
Owner Author

riii111 commented Oct 8, 2024

memo

HTTPOnlyなCookiesについて。

  • 実行環境の違い

    • クライアントサイド
      ブラウザ環境で実行され、Cookieへのアクセスにセキュリティ制限がある。
    • サーバーサイド
      Node.js環境で実行され、HTTPリクエストの全ての情報(Cookieを含む)にアクセス可能。
  • セキュリティメカニズム
    HttpOnly フラグは、ブラウザに対する指示であり、JavaScriptからのアクセスを制限する。
    サーバーサイドではこの制限が適用されず、全てのCookieを読み取れる。

  • 信頼性の違い
    クライアントサイドは潜在的に不正な改曜を受ける可能性がある。
    サーバーサイドは制御された安全な環境とみなされる。

@riii111 riii111 pinned this issue Oct 17, 2024
@riii111 riii111 unpinned this issue Oct 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend bug Something isn't working frontend
Projects
None yet
Development

No branches or pull requests

1 participant