C#アプリからDAuth(WebAuthnを使ってみた)
1 はじめに
2022年3月、FIDO Allianceよりパスキーが発表され、様々なWebサービスで利用されています。最近では、任天堂、 ソニー・インタラクティブエンタテインメント(SIE)などのアカウント認証でも利用され、さまざまな分野で利用されるようになってきました。パスワードレス認証技術であるパスキーは、安全・簡単に、なりすましやアカウント乗っ取りを未然に防ぐことができる仕組みです。
2 なぜパスキー認証がWebアプリで利用可能なのか?
現在、FIDO2認証はW3Cに準拠したWebブラウザでパスキー認証が搭載されており、Webアプリは特別なソフトウェアを追加することなく、パスキー認証をインプリメントすることができます。パスキー認証の実装は、ページ内のjavascriptからnavigator.credentialsオブジェクトを通じて、パスキーの登録や認証要求を行います。navigator.credentialsとパスキーの間ではCTAP2(client to authenticator protocol)と呼ばれる、通信プロトコルを用いて情報交換が行われます。
Webアプリは、OSやブラウザレベルでパスキーとの通信をサポートしたため、標準的なコーディングでパスキー認証が利用可能となりました。
3 パスキー認証をC#(.Net)アプリで利用可能か?
ネイティブアプリケーションよりパスキーを利用するには、いくつか方法がありますが、Microsoftが2023/06/13に公開した、WindowsWebAuthn API※1 を使っています。今回のターゲットは、C#(.Net)アプリで利用することを目的としているのでWindowsWebAuthn APIをラッピングしている、Yoq.WindowsWebAuthn※2 ライブラリを利用し、動作確認をしました。
※1 WindowsAuthn API : https://learn.microsoft.com/ja-jp/windows/win32/webauthn/-webauthn-portal
※2 Yoq.WindowsWebAuthn : https://github.com/dbeinder/Yoq.WindowsWebAuthn/tree/master
4 実装例
記事の関係で、認証部分をピックアップして、FIDO2認証を行うDAuthへPostするメソッドと、Yoq.WindowsWebAuthnを使ってパスキーに認証要求を行うソースをピックアップして処理概要をご説明します。
DAuthへHttp(Post):
public String Post( String path, Object postParam ) {
HttpRequestMessage request = BuildHttpRequestMessage( HttpMethod.Post, path);
//parse send param
string jsonStr = “”;
try
{
jsonStr = JsonSerializer.Serialize(postParam);
request.Content = new StringContent(jsonStr, Encoding.UTF8, “application/json”);
}
catch
{
throw;
}
string resBodyStr;
HttpStatusCode resStatusCoode = HttpStatusCode.NotFound;
Task<HttpResponseMessage> response;
try
{
response = client.SendAsync(request);
resBodyStr = response.Result.Content.ReadAsStringAsync().Result;
resStatusCoode = response.Result.StatusCode;
}
catch (HttpRequestException e)
{
// UNDONE: 通信失敗のエラー処理
throw e;
}
if (!resStatusCoode.Equals(HttpStatusCode.OK))
{
// UNDONE: レスポンスが200 OK以外の場合のエラー処理
throw new Exception($”http error status ({resStatusCoode})”);
}
if (String.IsNullOrEmpty(resBodyStr))
{
// UNDONE: レスポンスのボディが空の場合のエラー処理
throw new Exception($”response body is Null Or Empty“);
}
// 中身のチェックなどを経て終了。
return resBodyStr;
}
Yoq.WindowsWebAuthnを利用しパスキー認証:
public dynamic VerifyCredential(dynamic endpoint)
{
//DAuthの、AssertionOptionsを呼び出すためのパラメータを生成します
var postData = new
{
data =
{
user_id = “test-user”,
},
meta =
{
origin = “https://test.com”,
x_dauth_api_key = “XXXXXXXXXXXXXXXXXXXXXXXXXX”,
}
};
logger.Debug($”AssertionStart postData:{JsonSerializer.Serialize(postData)}“);
//DAuthを呼び出し、AssertionOptionsを取得します
AssertionOptions assertionOptions = null;
var assertionResponse = Post(endpoint.AssertionStart, postData);
try
{
ErrorResponse errorResponse = JsonSerializer.Deserialize<ErrorResponse>(assertionResponse);
if (errorResponse.Errors.Length > 0)
{
return errorResponse;
}
assertionOptions = JsonSerializer.Deserialize<AssertionOptions>(assertionResponse);
logger.Debug($”assertionOptions:{JsonSerializer.Serialize(assertionOptions)}“);
}
catch ( Exception e )
{
logger.Error(e);
}
//GetAssertionの呼び出し
hWnd = (hWnd != IntPtr.Zero) ? WinApiHelper.GetForegroundWindow() : hWnd;
logger.Debug($”hWnd = {hWnd}“);
var cancelSource = this.DoTimeout ? new CancellationTokenSource(TimeSpan.FromSeconds(30)) : null;
AuthenticatorAssertionRawResponse assertResponse;
var res = WebAuthn.GetAssertion(hWnd, assertionOptions, TargetOrigin, out assertResponse, cancelSource?.Token);
//GetAssertionAPIのエラーチェック
switch (res)
{
case WebAuthnResult.NotSupported:
case WebAuthnResult.TimeoutExpired:
case WebAuthnResult.InvalidParameters:
case WebAuthnResult.UserCancelled:
case WebAuthnResult.CredentialExists:
logger.Error($”Error has occurred status={res}“);
return new { status = res };
default:
logger.Error($”WebAuthn.GetAssertion status ={res}“);
break;
}
//認証器からのAssertionResponseをDAuthに送信するために加工します
var attestationResponse = new
{
id = Base64UrlTextEncoder.Encode(assertResponse.Id),
rawId = Base64UrlTextEncoder.Encode(assertResponse.RawId),
type = assertResponse.Type,
extensions = assertResponse.Extensions,
response = new
{
authenticatorData = Base64UrlTextEncoder.Encode(assertResponse.Response.AuthenticatorData),
clientDataJson = Base64UrlTextEncoder.Encode(assertResponse.Response.ClientDataJson),
signature = Base64UrlTextEncoder.Encode(assertResponse.Response.Signature),
},
};
//DAuthに認証結果を設定し、認証判定をリクエストします
var assertionResponseParam = new
{
data = attestationResponse,
meta = metaData
};
logger.Info($”AssertionResponseParam:{JsonSerializer.Serialize(assertionResponseParam)}“);
var assertionResultJson = Post(endpoint.AssertionResponse, assertionResponseParam);
//認証結果を受け取ります
logger.Debug($”assertionResponseResult = {assertionResultJson}“);
AssertionResponseResult assertionResponseResult = JsonSerializer.Deserialize<AssertionResponseResult>(assertionResultJson);
return assertionResponseResult;
}
認証フロー:
5 まとめ
WebAuthnによるFIDO2認証をネイティブアプリケーションに追加する場合の実装例を掲載してみました。Webアプリ以外でもネイティブアプリでFIDO2認証を利用できるのは、利用幅が広がりますね。DAuthを利用すると、あなたのWebアプリ内にFIDO2認証サーバーを構築することなく、パスキー認証を追加することができます。さまざまなシチュエーションでパスキーが利用されればよいと思います。
このコラムにコメントする
コメントは承認制とさせていただいているため、反映まで少しお時間をいただきます。あらかじめご了承ください。