ASP.NETでMagicOnionに認証機能を実装する


# はじめに
この記事は[PONOS Advent Calendar 2024](https://qiita.com/advent-calendar/2024/ponos)13日目の記事です。
前回は[@kerimeka](https://qiita.com/kerimeka)さんでした!

MagicOnionは、Unityなどのリアルタイムアプリケーションで利用可能な軽量なgRPCベースのフレームワークです。  
この記事では、ASP.NETの認証機能を利用してMagicOnion APIへのアクセス制限を行う方法を解説します。


## 使用する技術

- ASP.NET Core 8.0
- MagicOnion
- JSON Web Token (JWT)
- Unityクライアント

## ゴール

1. **ASP.NET Core**でJWTベースの認証を設定。
2. **MagicOnionサービス**で認証されたクライアントのみアクセス可能にする。
3. **Unityクライアント**からのトークン付き通信を実装。

## ステップ1: JWT認証をASP.NET Coreに追加

まず、ASP.NET CoreプロジェクトでJWT認証を設定します。

```csharp
// Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// JWT認証の設定
var key = Encoding.UTF8.GetBytes("YourSecureKeyHere");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "YourIssuer",
            ValidAudience = "YourAudience",
            IssuerSigningKey = new SymmetricSecurityKey(key)
        };
    });

builder.Services.AddMagicOnion();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapMagicOnionService<MyService>();

app.Run();
```

---

## ステップ2: MagicOnionで認証をチェックする

次に、MagicOnionサービスで認証済みユーザーのみがアクセスできるように設定します。

```csharp
using MagicOnion;
using MagicOnion.Server;

[Authorize] // ASP.NET Coreの認証属性を使用
public class MyService : ServiceBase<IMyService>, IMyService
{
    public async UnaryResult<string> GetSecureData()
    {
        var user = Context.GetHttpContext().User;

        if (!user.Identity.IsAuthenticated)
        {
            throw new UnauthorizedAccessException("Unauthorized");
        }

        return "This is secure data!";
    }
}
```


---


## ステップ3: トークンを生成するエンドポイントの実装

認証用のJWTトークンを発行するAPIを実装します。

```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    [HttpPost("token")]
    public IActionResult GenerateToken([FromBody] LoginRequest request)
    {
        if (request.Username == "user" && request.Password == "password") // 仮の認証
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YourSecureKeyHere"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: "YourIssuer",
                audience: "YourAudience",
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds
            );

            return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        }

        return Unauthorized();
    }
}

public class LoginRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
}
```


---

## ステップ4: Unityクライアントからのトークン付きリクエスト

Unityクライアントでトークンを取得し、MagicOnion APIにリクエストを送信します。

```csharp
using MagicOnion.Client;
using Grpc.Net.Client;
using System.Net.Http;
using System.Threading.Tasks;

public class UnityClient : MonoBehaviour
{
    private async Task Start()
    {
        // トークンを取得
        var httpClient = new HttpClient();
        var response = await httpClient.PostAsync("https://yourserver/api/auth/token",
            new StringContent("{\"username\":\"user\",\"password\":\"password\"}", Encoding.UTF8, "application/json"));
        
        var responseBody = await response.Content.ReadAsStringAsync();
        var token = JsonUtility.FromJson<TokenResponse>(responseBody).token;

        // MagicOnionクライアントを作成
        var channel = GrpcChannel.ForAddress("https://yourserver", new GrpcChannelOptions
        {
            HttpClient = new HttpClient { DefaultRequestHeaders = { Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token) } }
        });

        var client = MagicOnionClient.Create<IMyService>(channel);
        var result = await client.GetSecureData();
        Debug.Log(result);
    }

    [System.Serializable]
    private class TokenResponse
    {
        public string token;
    }
}
```
# 終わりに

いかがでしたでしょうか。
ASP.NETの認証機能を利用してMagicOnion APIへのアクセス制限を行う方法でした。
他にも色々な方法があると思いますので、色々実験していきたいと思います。

次回は[@caramel_cafe](https://qiita.com/caramel_cafe)さんの「Unity 音声ファイル インポート設定」です！