ASP.NET Core(C#)でWebAuthn/FIDO2多要素認証(パスキー)を実装する方法|fido2-net-libとIdentity対応

ASP.NET(C#)のアプリに、SMSやTOTPに頼らない「フィッシング耐性の高い多要素認証」を入れたいなら WebAuthn / FIDO2(パスキー)が有力です。本記事では、fido2-net-lib を使って最短で実装する方法と、設計・運用で失敗しないための実務ポイントをまとめます。

目次

WebAuthn / FIDO2 を ASP.NET(C#)で使うと何が嬉しいのか

WebAuthn / FIDO2(パスキー)の強みは、フィッシング耐性とユーザー体験を両立しやすいことです。サーバーは公開鍵を保持し、ユーザーは端末側(Windows Hello、Touch ID、Android の画面ロック、セキュリティキーなど)で署名します。秘密鍵は端末から出ないため、パスワード漏えいのような「共有秘密」が起点の事故を減らせます。

多要素認証として使う場合も、単に「第二要素を増やす」だけでなく、“本人がその場で端末のロック解除をした”(生体認証/PIN などの user verification)を要求でき、運用ポリシーを作りやすいのが特徴です。

方式強み弱み・注意点向いている場面
SMS導入が簡単、ユーザーが慣れているSIM スワップや転送、フィッシングに弱い。送信コストが増える暫定導入、低リスク領域
TOTP(認証アプリ)SMS より強い。Identity で標準サポートがあるフィッシングで入力させられる。端末移行で詰みやすい既存の 2FA を早く整備したい
WebAuthn / FIDO2(パスキー)フィッシング耐性が高い。UX が良い(生体/PIN)実装と運用設計(鍵管理・復旧)が必須重要アカウント、管理画面、B2B、パスワード廃止

WebAuthn / FIDO2 / パスキーの整理(混同しやすいポイント)

「FIDO2 を入れたい」「WebAuthn を使いたい」「パスキー対応したい」は、現場ではほぼ同じ話として出てきます。ただし、役割が違うので一度整理しておくと設計がブレません。

用語ざっくり説明ASP.NET 側で主にやること
WebAuthnブラウザの Web Authentication API。登録(create)と認証(get)の API を規定。チャレンジ等のオプションを生成し、返ってきた応答を検証する。
FIDO2WebAuthn + 認証器との通信規格(CTAP2)などを含む仕様群。Web アプリとしては WebAuthn フローの実装が中心。
パスキー(Passkey)WebAuthn のクレデンシャルの一般向け呼称。同期型(マルチデバイス)も増加。サーバー視点では WebAuthn と同じ。署名カウンタなど挙動差に注意。

認証器の種類を理解すると要件が決めやすい

WebAuthn では、ユーザーが使う「認証器(Authenticator)」の種類によって、UX も運用も変わります。MFA で導入する場合は、まずこの分類をチームで共有しておくと判断が速くなります。

種類例特徴導入のコツ
プラットフォーム認証器Windows Hello / Touch ID / Android 端末ロック端末内蔵。生体/PIN とセットで使われることが多く UX が良い。社内PC配布や端末管理がある環境と相性が良い。
ローミング認証器USB/NFC セキュリティキー(YubiKey など)持ち運べる。端末が変わっても使いやすい。予備キー運用もしやすい。「予備キーの登録」を最初から促すと復旧が楽。
同期型パスキーパスワードマネージャー等で同期されるパスキー複数デバイスで使える。利便性が高い一方、カウンタ等の挙動が昔の“単体鍵”と違う場合がある。署名カウンタは“参考値”として扱い、復旧設計と監査ログを厚くする。

まず押さえるべき前提条件(ここがズレると必ず詰まる)

  • HTTPS と Secure Context:本番は必須。開発でもなるべく https を使う(環境差が減る)。
  • Origin(スキーム+ホスト+ポート):https://app.example.com のように“完全一致”で扱われる。リバースプロキシ配下では特に注意。
  • RP ID(rp.id):通常はドメイン(例:example.com)。サブドメイン共有を狙うなら rpId を上位にするが、権限範囲も広がるので設計判断が必要。
  • チャレンジの一回性:登録・認証オプション(challenge を含む)は、使ったら必ず破棄する。
  • 負荷分散:オプション保存(セッション/キャッシュ)が複数台で破綻しない設計にする。

特に Origin/RP ID は “動かない” だけでなく、誤設定すると意図しない範囲で鍵が有効になる可能性があります。環境ごとに方針を決め、設定値をコード/設定管理に載せるのがおすすめです。

ドキュメントやコードサンプルはどこにある?

ASP.NET(C#)で WebAuthn / FIDO2 を実装する場合、まず読むべき一次資料は次の 3 つです。

  • 実装ライブラリ:fido2-net-lib(.NET 用 FIDO2 / WebAuthn ライブラリ)
  • Identity 統合の実例:ASP.NET Core Identity + FIDO2 のサンプル(damienbod)
  • フロント API の根拠:MDN の Web Authentication API と W3C 仕様
種別リソース何が分かるか最短の使い方
ライブラリ本体(デモ付き)passwordless-lib/fido2-net-lib(GitHub)DI 設定、登録/認証の雛形、デモ実装。README の Quick Start → Demo Controller を読む
ドキュメントPasswordless.dev Documentation(fido2-net-lib)目的・入口・関連リンク。導入前の社内説明資料づくりに使う
Identity 統合サンプルdamienbod/AspNetCoreIdentityFido2Mfa(GitHub)Identity と MFA/Passwordless の統合、DB マイグレーション。自分のアプリの構成に近いプロジェクトを動かす
解説記事ASP.NET Core Identity with FIDO2 WebAuthn MFA(damienbod)画面連携、フロー、モデル設計の考え方。チーム内の合意形成・レビュー観点の共有に使う
フロント APIWeb Authentication API(MDN)create/get の仕様と関連 API。JS 実装の“根拠資料”としてブックマーク
仕様(規格)W3C WebAuthn Level 2Origin、rpIdHash、データ構造の厳密定義。エッジケースの調査(挙動が怪しい時の最終確認)
Windows の補足WebAuthn APIs for passwordless authentication on Windows(Microsoft Learn)Windows Hello / FIDO2 セキュリティキーと WebAuthn の関係。社内の Windows 端末要件の説明に使う

補足:.NET 10 の ASP.NET Core Identity にはパスキーが標準搭載された

.NET 10 世代では、ASP.NET Core Identity にパスキー登録・認証の機能が標準搭載され、Blazor Web App テンプレートにも組み込まれています。公式ドキュメントでは、パスキーは WebAuthn を基盤とした「フィッシング耐性のある認証手段」として整理され、登録(attestation)と認証(assertion)の概念も説明されています。

一方で、Identity のパスキー実装は「Identity の認証シナリオにスコープされ、汎用 WebAuthn ライブラリではない」こと、既定では attestation の検証を行わないこと、そしてパスキーは 2FA(第二要素)としてではなく“第一要素”として扱われる点が明記されています。つまり「パスワード + パスキー」を 2FA として厳密に構成したい場合は、追加設計(または別ライブラリ)が必要になります。

選択肢向いているケース注意点
Identity 標準のパスキー(.NET 10+)Identity を使っていて、一般的なパスキー体験を最短で提供したい既定では attestation 検証なし/2FA としての標準実装ではない
fido2-net-libIdentity に縛られず WebAuthn を実装したい、attestation や先端機能も扱いたい設計(鍵管理・復旧)を自分で持つ必要がある
自前実装研究・学習、特殊要件(ただし高リスク)CBOR/COSE/検証の実装ミスが脆弱性に直結しやすい

推奨アプローチ:fido2-net-lib を使った実装(サーバー側が主戦場)

WebAuthn は「フロントで navigator.credentials を呼べば終わり」ではなく、サーバー側で署名検証・チャレンジ検証・Origin/RP ID 検証・データ保存を正しく実装して成立します。暗号や CBOR/COSE を自前で組むのは現実的ではないため、まずは fido2-net-lib の採用をおすすめします。

fido2-net-lib は .NET Foundation のプロジェクトとして公開されており、登録(attestation)と認証(assertion)の検証ロジック、ASP.NET Core 向けのヘルパー(Fido2.AspNet)などが揃っています。README にはデモ実装やサンプルへの導線もあるため、実装の入口として強力です。

インストールと基本設定

NuGet でパッケージを追加します(README では .NET 8 以降が要件として記載されています)。

dotnet add package Fido2
dotnet add package Fido2.AspNet

次に「サーバーのドメイン/表示名/許可する Origin」を正しく設定します。ここがズレると、登録も認証も通りません。

// Program.cs(例)
builder.Services.AddFido2(options =>
{
    // RP ID として扱うドメイン(= WebAuthn の rp.id)
    options.ServerDomain = "example.com";


// 認証器の UI に表示される名称(ユーザーが確認する)
options.ServerName = "Example App";

// 許可する Origin(複数環境があるなら列挙)
options.Origins = new HashSet<string>
{
    "https://example.com",
    "https://app.example.com"
};


});

おすすめの構成(最小構成でも“設計”が要る)

実装を始める前に、「どこに何を保存するか」を決めておくと詰まりません。最低限、以下の 2 つのストレージが必要です。

  • ChallengeStore:登録/認証の options(challenge を含む)を一時保存して取り出す場所(セッション、分散キャッシュ、DB など)
  • CredentialStore:ユーザーに紐づくクレデンシャル(公開鍵等)を永続化する場所(DB)
ストレージ保存するもの推奨注意点
ChallengeStoreCredentialCreateOptions / AssertionOptions(JSON)単体なら Session、複数台なら Redis 等使い回し禁止(取得したら削除)
CredentialStoreCredentialId / PublicKey / UserHandle / Counter などRDB(EF Core)ユーザーは複数鍵を持つ前提で設計

“とりあえず動かす” ならセッション保存が楽ですが、ロードバランサ配下では破綻しやすいので、早い段階で Redis などの分散キャッシュに寄せる計画を立てると後が楽です。

セッションに options を保存する場合のミドルウェア例

// Program.cs(例:Session を使う場合)
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
    options.IdleTimeout = TimeSpan.FromMinutes(10);
});

var app = builder.Build();
app.UseSession();

エンドポイント設計(MFA として使いやすい形)

登録(attestation)と認証(assertion)それぞれで「options 生成」と「検証」を分けるのが定番です。これを分けることで、チャレンジの一回性や有効期限を扱いやすくなります。

フェーズエンドポイント例HTTP役割実務ポイント
登録/webauthn/attestation/optionsPOST登録用オプション(challenge 等)を返すログイン済み限定にする(勝手に鍵を追加されない)
登録/webauthn/attestationPOST応答を検証し、DB に保存直前の再認証(パスワード再入力等)を検討
認証/webauthn/assertion/optionsPOST認証用オプション(challenge 等)を返すMFA 用なら「第1段階後」の一時状態で呼べるようにする
認証/webauthn/assertionPOST署名を検証し、ログイン完了にする成功時に最終的な認証 Cookie を発行

DBに保存すべきデータ(最低限)

WebAuthn は「登録した端末(認証器)ごと」にクレデンシャルが増えます。ユーザーごとに 1 件とは限らないため、ユーザーとは別テーブルで持つのが安全です。

項目保存する理由実装メモ
CredentialId認証器の識別子。認証時にどの鍵か特定する。byte[] を Base64URL で保存すると扱いやすい。
PublicKey(COSE)署名検証に必要。ライブラリが返す形式でそのまま保存するのが楽。
UserHandleユーザーとクレデンシャルの紐づけ確認に使う。Identity の UserId を byte[] に変換したものを使うことが多い。
SignatureCounterクローン検知(ベストエフォート)。同期型パスキーでは単調増加しない場合があるため「参考値」として扱う。
CredType / AAGUID(任意)鍵の種類・識別、将来のポリシー制御に役立つ。監査や “特定の認証器のみ許可” の要件がある場合に有用。
登録名(任意)利用者が複数鍵を見分けるため。UX が大きく改善する(例:会社PC/自宅/予備キー)。

AuthenticatorSelection のおすすめ(MFA と Passwordless で変わる)

WebAuthn のオプションは細かく調整できますが、最初は “目的別のテンプレ” を作っておくと実装が安定します。

目的UserVerificationResidentKeyAllowedCredentialsひとこと
MFA(パスワード+パスキー)preferred(要件次第で required)discouraged/optionalユーザーの登録済み鍵を指定まずこの形が安全。既存ログインに足しやすい。
パスキー優先(ユーザー名あり)requiredpreferred/requiredユーザーの登録済み鍵を指定UX が良い。復旧設計(予備鍵/コード)が重要。
ユーザー名レス(discoverable)requiredrequired空(または最小)設計難度が上がるので段階導入がおすすめ。

attestation をどう扱うか(企業要件で差が出る)

多くの一般サイトでは、プライバシーと実装コストの観点から attestation は “none” で十分です。一方で「許可された認証器のみ使わせたい」「強い証跡が必要」など企業要件がある場合は、attestation を扱う設計が必要になります。

fido2-net-lib には Metadata Service(MDS)などに関する設定項目もあり、必要に応じて段階的に強化できます(ただし、最初から全部やると難易度が跳ね上がります)。

サーバー側の雛形(登録)

以下は fido2-net-lib の README の流れをベースに、要点が分かる形に整理した例です。実プロダクトでは、ユーザー取得/永続化/一時ストレージの実装を自分のアプリに置き換えてください。

// 例:Attestation(登録)オプション生成
[HttpPost("/webauthn/attestation/options")]
public IActionResult CreateAttestationOptions([FromBody] RegisterRequest req)
{
    var user = _userRepo.GetByName(req.UserName);
    if (user == null) return NotFound();

    // 既存鍵(重複登録防止)
    var existing = _credRepo.ListByUser(user.Id)
        .Select(c => c.Descriptor)
        .ToList();

    var options = _fido2.RequestNewCredential(new RequestNewCredentialParams
    {
        User = new Fido2User
        {
            Id = user.UserHandleBytes,   // byte[]
            Name = user.UserName,
            DisplayName = user.DisplayName
        },
        ExcludeCredentials = existing,
        AuthenticatorSelection = AuthenticatorSelection.Default,
        AttestationPreference = AttestationConveyancePreference.None
    });

    _challengeStore.Save(user.Id, "attestation", options.ToJson());
    return new JsonResult(options);
}

// 例:Attestation(登録)検証
[HttpPost("/webauthn/attestation")]
public async Task<IActionResult> MakeAttestation([FromBody] AuthenticatorAttestationRawResponse clientResponse)
{
    var json = _challengeStore.Pop(UserId(), "attestation");
    var options = CredentialCreateOptions.FromJson(json);

    IsCredentialIdUniqueToUserAsyncDelegate callback = async (args) =>
    {
        var owners = await _credRepo.FindUsersByCredentialIdAsync(args.CredentialId);
        return owners.Count == 0;
    };

    var result = await _fido2.MakeNewCredentialAsync(new MakeNewCredentialParams
    {
        AttestationResponse = clientResponse,
        OriginalOptions = options,
        IsCredentialIdUniqueToUserCallback = callback
    });

    _credRepo.Add(new StoredCredential
    {
        Descriptor = new PublicKeyCredentialDescriptor(result.Id),
        PublicKey = result.PublicKey,
        UserHandle = options.User.Id,
        SignatureCounter = result.Counter,
        CredType = result.CredType,
        RegDate = DateTimeOffset.UtcNow
    });

    return Ok(new { status = "ok" });
}

サーバー側の雛形(認証)

認証(assertion)では、ユーザーに紐づく CredentialId を AllowedCredentials に入れて「このユーザーの鍵だけ使える」状態にするのが基本です。ユーザー名レス(discoverable credential)に踏み込む場合は設計が変わるため、まずは “ユーザー名あり” の MFA から始めるのが安全です。

// 例:Assertion(認証)オプション生成
[HttpPost("/webauthn/assertion/options")]
public IActionResult CreateAssertionOptions([FromBody] LoginRequest req)
{
    var user = _userRepo.GetByName(req.UserName);
    if (user == null) return NotFound();

    var allowed = _credRepo.ListByUser(user.Id)
        .Select(c => c.Descriptor)
        .ToList();

    var options = _fido2.GetAssertionOptions(new GetAssertionOptionsParams
    {
        AllowedCredentials = allowed,
        UserVerification = UserVerificationRequirement.Preferred
    });

    _challengeStore.Save(user.Id, "assertion", options.ToJson());
    return new JsonResult(options);
}

// 例:Assertion(認証)検証
[HttpPost("/webauthn/assertion")]
public async Task<IActionResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
{
    var json = _challengeStore.Pop(UserId(), "assertion");
    var options = AssertionOptions.FromJson(json);

    var stored = _credRepo.GetByCredentialId(clientResponse.Id);
    if (stored == null) return Unauthorized();

    IsUserHandleOwnerOfCredentialIdAsync callback = async (args) =>
    {
        var creds = await _credRepo.ListByUserHandleAsync(args.UserHandle);
        return creds.Any(c => c.Descriptor.Id.SequenceEqual(args.CredentialId));
    };

    var res = await _fido2.MakeAssertionAsync(new MakeAssertionParams
    {
        AssertionResponse = clientResponse,
        OriginalOptions = options,
        StoredPublicKey = stored.PublicKey,
        StoredSignatureCounter = stored.SignatureCounter,
        IsUserHandleOwnerOfCredentialIdCallback = callback
    });

    stored.SignatureCounter = res.Counter;
    stored.LastUsed = DateTimeOffset.UtcNow;
    _credRepo.Update(stored);

    return Ok(new { status = "ok" });
}

フロントエンド(JavaScript)の実装ポイント

WebAuthn のリクエスト/レスポンスには ArrayBuffer が混ざるため、そのまま JSON でやり取りできません。サーバーから受け取った Base64URL 文字列を Uint8Array に戻し、ブラウザ API に渡し、結果を Base64URL にしてサーバーへ返す…という「変換レイヤー」が必須です。

// Base64URL <-> ArrayBuffer(簡易ヘルパー例)
function bufferToBase64Url(buffer) {
  const bytes = new Uint8Array(buffer);
  let str = "";
  for (const b of bytes) str += String.fromCharCode(b);
  return btoa(str).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
function base64UrlToBuffer(base64url) {
  const base64 = base64url.replaceAll("-", "+").replaceAll("_", "/") + "===".slice((base64url.length + 3) % 4);
  const str = atob(base64);
  const bytes = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i);
  return bytes.buffer;
}

実務では、この変換ロジックを自作して微妙な差分でハマるより、fido2-net-lib のデモや統合サンプルの実装をベースに合わせる方が安全です。

ASP.NET Core Identity と組み合わせて「MFA」として提供する設計

「パスワード + WebAuthn/FIDO2」の多要素認証をしたい場合、Identity のサインインを二段階に分けるのが分かりやすい実装になります。

段階ユーザー操作サーバー側の状態実装の要点
第1段階ユーザー名 + パスワード「MFA 未完了」の一時状態最終ログイン Cookie をまだ発行しない(または 2FA 用の一時 Cookie を使う)
第2段階パスキー/セキュリティキーで確認完全ログイン状態assertion 検証成功後にサインインを完了させる

damienbod のサンプルは、この「Identity 連携」を具体例で学ぶのに最適です。Razor Pages の UI、DB マイグレーション、複数鍵登録、Passwordless の派生まで含まれており、プロダクトに近い形で理解できます。

登録(鍵の追加)を安全にするコツ

WebAuthn の “登録” は、運用上もっとも危険な操作です。攻撃者がセッションを奪っている状態で登録を許すと、攻撃者の認証器をアカウントに紐づけられてしまいます。最低限、次を検討してください。

  • 鍵登録ページはログイン済みのみアクセス可能にする
  • 鍵登録の直前に再認証を要求する(パスワード再入力、既存パスキーでの確認など)
  • 登録・削除は監査ログに残す

鍵を「管理させる」画面は最初から作る

MFA を実装して終わりではありません。運用に耐えるには、最低限次の UI が必要です。

  • 登録したパスキー/セキュリティキーの一覧(名称・登録日・最終利用日時)
  • 鍵の名称変更(例:「会社PC」「自宅Mac」「YubiKey(予備)」)
  • 鍵の削除(紛失・退職・機種変更)
  • 復旧手段(リカバリーコード発行、予備キー登録の促進、サポート窓口フロー等)

自前実装(Web Authentication API 直叩き)を選ぶ場合の現実

ライブラリを使わず、ブラウザの Web Authentication API を直接扱うこと自体は可能です。クライアント側は登録が navigator.credentials.create()、認証が navigator.credentials.get() です。

ただし、サーバー側の検証は難易度が高く、実装ミスがそのまま脆弱性になり得ます。自前実装では、少なくとも次の領域を自力で正しく実装する必要があります。

領域具体的な作業難所
データ形式CBOR のデコード、COSE 公開鍵の取り扱い、Base64URL 変換実装ミスがそのまま脆弱性になる
検証clientDataJSON / authenticatorData の検証、rpIdHash、署名検証、challenge 検証仕様が多く、テストが大変
attestationattestationObject の解析、証明書チェーン検証、フォーマット対応「対応しないと動かない」ケースが出る
拡張PRF / Large Blob / credProps などの拡張の扱い将来の拡張で互換性の問題が出やすい

WebAuthn のクライアント~サーバー間のやり取りを掴む目的なら、nginx の auth_request と組み合わせた WebAuthn のサンプル実装を見るのも参考になります(ASP.NET ではありませんが、フロー理解に役立ちます)。

newhouseb/nginxwebauthn(GitHub)

セキュリティと運用で失敗しないためのチェックリスト

WebAuthn / FIDO2 は「強い認証」ですが、実装や運用が雑だと強みを相殺します。最後に、ASP.NET 実装で特に効くポイントをチェックリスト化します。

観点やることなぜ重要か
Origin / RP ID本番・検証・開発で URL と rpId を設計し、設定値を環境ごとに分離ミスると登録済み鍵が使えない/意図しない範囲で鍵が有効になる
チャレンジ管理一度使った options は必ず破棄。期限(例:数分)も設定リプレイ攻撃やバグの温床を減らす
User VerificationMFA の強度を担保したいなら required を検討「所持」だけでなく本人性(生体/PIN)を要求できる
鍵追加の防御登録の直前に再認証を要求、登録・削除の監査ログを残すセッション乗っ取り時の “鍵追加” を防ぐ
レート制限options 生成/検証の API を IP・ユーザー単位で制限総当たり・リソース枯渇を防ぐ
復旧設計リカバリーコード、予備キー、サポートフローを用意紛失・機種変更で詰むユーザーを出さない

よくあるハマりどころと対処

症状原因の典型対処
登録・認証が即失敗する(Origin エラー)options.Origins と実際の URL が一致していない/http でアクセスしているhttps 化、Origin を正確に列挙、リバースプロキシ配下の URL を確認
NotAllowedError が出るユーザーがキャンセル/生体認証未設定/利用可能な認証器がないUI で誘導(Windows Hello の設定案内、セキュリティキー挿入案内)
InvalidStateError(すでに登録済み)同じ認証器を重複登録しようとしているExcludeCredentials を正しく設定し、鍵の管理 UI を提供
署名検証が通らない保存した PublicKey/CredentialId の取り扱い(Base64URL変換)ミスバイナリ→文字列変換の実装を統一し、サンプルと差分を減らす
負荷分散後に失敗するoptions をセッションに置いていて別サーバーに飛ぶsticky session または分散キャッシュ(Redis 等)に移す

まとめ:実装を成功させる最短ルート

  • まずは fido2-net-lib の Quick Start とデモを動かし、登録(attestation)と認証(assertion)のデータの流れを掴む。
  • ASP.NET Core Identity を使っているなら、AspNetCoreIdentityFido2Mfa の統合サンプルで UI/DB/フローを学び、自分のアプリに寄せる。
  • 運用に必要な「鍵管理画面」「復旧フロー」「監査ログ」「レート制限」まで含めて設計する。

WebAuthn / FIDO2 は、導入できればログインの安全性とユーザー体験を両立できます。逆に言えば、細部(Origin/RP ID、チャレンジの一回性、データ保存)を雑にすると簡単に詰みます。まずは動くサンプルから入り、段階的に自分のアプリに最適化していくのが近道です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次