かぐや様は告らせたい(24)

石上とミコの接近スピード半端ないな。会長と副会長のときと比べてスピード感が違う。恋愛頭脳戦では、かぐやよりもミコの方が試合巧者だ。ただ、ミコルート確定が早いのはちょっともったいないかな。大仏も捨てがたいので、大仏と石上の絡みがもっと見たかった。石上モテモテだけど、つばめ先輩に相応しい男になるために努力してきた姿を見てきただけに、今となってはなんら違和感ない。さすが裏主人公。

そしてついに最終章突入。四条グループが四宮グループにTOBし、かぐやは生徒会メンバーの前から姿を消し、この状況で会長に出来ることあるのか?1年時の生徒会選挙での大立ち回りがまだ明らかになってないけど、案外、そのときの大立ち回りがかぐやを救うヒントになったりして。いつか言った、「俺なら絶対かぐやを手放したりしないのに」を会長は有言実行してくれるに違いない。

HttpClient を使ってクライアントを OAuth 2.0 の認可コードフローと PKCE に対応させる

.NET の OAuth クライアントは IdentityModel が定番だけど、あえて IdentityModel を使わず、HttpClient だけで OAuth2.0 の認可コードフローを通す。PKCE にも対応.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace SampleWebApiClient
{
    class Program
    {
        const string IdentityServerAddress = "IdentityServerのアドレス";
        const string WebApiAddress = "Web API のアドレス";
        const string ClientId = "クライアント ID";
        const string Scope = "API スコープ";
        const string RedirectUri = "リダイレクト URI";

        static async Task Main(string[] args)
        {
            // PKCE(認証コード横取り対策)
            string codeVerifier;
            using (var rng = RNGCryptoServiceProvider.Create())
            {
                var bytes = new byte[32];
                rng.GetBytes(bytes);
                codeVerifier = Convert.ToBase64String(bytes)
                    .Split('=')[0]
                    .Replace('+', '-')
                    .Replace('/', '_');
            }
            string challenge;
            using (var sha256 = SHA256.Create())
            {
                var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
                challenge = Convert.ToBase64String(challengeBytes)
                    .Split('=')[0]
                    .Replace('+', '-')
                    .Replace('/', '_');
            }

            // Web ブラウザで認証ページを表示
            var authorizeUrl =
                $"{IdentityServerAddress}/connect/authorize" +
                "?client_id=" + ClientId +
                "&response_type=code" +
                "&scope=" + Uri.EscapeUriString(Scope) +
                "&redirect_uri=" + Uri.EscapeUriString(RedirectUri) +
                "&code_challenge=" + challenge +
                "&code_challenge_method=S256"; ;
            var escapedUrl = authorizeUrl.Replace("&", "^&");
            Process.Start(new ProcessStartInfo("cmd", $"/c start {escapedUrl}")
            {
                CreateNoWindow = true,
            });

            // 認証コードを使ってアクセストークンを取得
            Console.Write("AuthorizationCode:");
            var code = Console.ReadLine();
            var tokenClient = new HttpClient
            {
                BaseAddress = new Uri(IdentityServerAddress),
            };
            var tokenResponse = await tokenClient.PostAsync(
                "/connect/token",
                new FormUrlEncodedContent(new Dictionary<string, string>
                {
                    ["grant_type"] = "authorization_code",
                    ["code"] = code,
                    ["client_id"] = ClientId,
                    ["redirect_uri"] = RedirectUri,
                    ["code_verifier"] = codeVerifier,
                }));
            var tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync();
            if (!tokenResponse.IsSuccessStatusCode)
            {
                Console.WriteLine(tokenResponseBody);
                goto END;
            }
            var accessToken = JsonDocument.Parse(tokenResponseBody)
                .RootElement
                .GetProperty("access_token")
                .GetString();

            // Web API を呼び出すクライアントは、
            // アクセストークンを取得するクライアントとは別にしておく
            var apiClient = new HttpClient
            {
                BaseAddress = new Uri(WebApiAddress),
            };

            // アクセストークンを設定
            apiClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Bearer", accessToken);

            // データを取得
            var response = await apiClient.GetAsync(
                "/api/products");
            response.EnsureSuccessStatusCode();
            Console.WriteLine(await response.Content.ReadAsStringAsync());

        END:
            Console.WriteLine("Enter で終了");
            Console.ReadLine();
        }
    }
}

openapi-generator の csharp-netcore ジェネレーターで HttpClient を使ったコードを生成する

--additional-properties=library=httpclient ではなく、--libary httpclient という罠。

openapi-generator-cli generate -i swagger.json \
  -g csharp-netcore \
  --library httpclient \
  --additional-properties=packageName=MyApi \
  -o temp/csharp-netcore

アルスラーン戦記(16)

ナルサスの親友だったシャガードは闇堕ちして、小物な悪党に成り果てていたな。ナルサスの策略が全的中する始末。ただ、悪の道に染まったといえ、ナルサスの元親友だったわけで、処刑は切り出しづらい。そんな状況で、一年間奴隷として働かせるアルスラーンの妙案は、なるほど上手いと思わせた。

ギランの海上商人たちの私兵を傘下に入れたことで、アンドラゴラスに要求された5万の兵は満たしただろうか。やけにアッサリとノルマ達成。いよいよ、再びエクバターナに向かうのだろう。

Small world

BUMP OF CHICKEN の新曲が配信されていたので、iTunes Store で購入した。すみっこぐらしの映画のタイアップというのは意外。チャマ復帰後初の曲になる。

叶わないままの夢はどんな光より綺麗で 変われないのに変わりたいままだから苦しくて

この歌詞が胸に刺さった。もうほんと、ズブリと。その傷にやわらかなメロディがじんわりと来る。繰り返し聴くたびに沁みた。

Small world

Small world

ムッシュさかい

福岡で美味いハンバーグの店を開拓していて、ずっと気になっていたのが、福岡市早良区田村にある『ムッシュさかい』。ようやく訪れることができた。

f:id:griefworker:20211204183113j:plain

ハンバーグ 200g の洋風セットを注文。洋風セットはライスとサラダとスープが付く。サラダはドレッシングの酸味が主調していて、昔こういうサラダ食べたなぁ、と懐かしい感じ。スープはコーンポタージュで、濃厚で美味だった。お代わりしたいくらい。

f:id:griefworker:20211204183111j:plain

ハンバーグのソースは、和風と洋風でかなり迷い、洋風にした。洋風はデミグラスソース。

f:id:griefworker:20211204183108j:plain

ミディアムの焼き加減で、しっかり肉汁閉じ込められていて、ナイフを入れると溢れだしてくる。ジューシー。溢れた肉汁のデミグラスソースが合わさって、新たなソースを味わっているような。200g 食べ終わるのあっという間だった。

f:id:griefworker:20211204183116j:plain

ムッシュさかい
〒814-0175 福岡県福岡市早良区田村7-2-3
1,380円(平均)880円(ランチ平均)
r.gnavi.co.jp

ブルーフォンセ

家族へのクリスマスプレゼントの1つとして、福岡三越のデパ地下にあるブルーフォンセでケーキを買って帰った。ブルーフォンセでケーキ買うのいつ以来だろうか。あまりにも前すぎて、もはや記憶に無い。地下街にもあったのが懐かしいな。

ガトーフレーズは定番。さっぱりした生クリームで、何個でも食べられるやつ。イチゴのショートケーキの中でも、自分的には五指に入る。

f:id:griefworker:20211202221418j:plain

ショコラクリュはチョコレート生地とガナッシュの組み合わせで、フランボワーズとか使ってないのが、むしろ高ポイント。チョコレートケーキはこうでなくては。かなり気に入った。

f:id:griefworker:20211202221416j:plain

シューロシェは子供用に買って帰った。カスタードたっぷりで実に美味しそうだったので、今度は自分用にも買おう。

f:id:griefworker:20211202221421j:plain

久しぶりに食べたブルーフォンセのケーキは、記憶どおりに美味だった。サイズはもうちょっと大きくていいけど、価格的にはデパ地下の中では良心的な方かな。最近は1個500円超とかザラだからねぇ。ブルーフォンセにはまた買いに行こうと思う。

関連ランキング:ケーキ | 西鉄福岡駅(天神)天神駅天神南駅