An open API service indexing awesome lists of open source software.

https://github.com/maremare/try-blazorwasm-standalone-singleorg

🧪📝 Blazor wasm (AAD認証) をGitHub Pages, Cloudflare Pagesへデプロイしてみます。
https://github.com/maremare/try-blazorwasm-standalone-singleorg

Last synced: about 2 months ago
JSON representation

🧪📝 Blazor wasm (AAD認証) をGitHub Pages, Cloudflare Pagesへデプロイしてみます。

Awesome Lists containing this project

README

          

# try-blazorwasm-standalone-singleOrg

| Status | Site URL |
|--|--|
| [![Deploy to GitHub Pages](https://github.com/MareMare/try-blazorwasm-standalone-singleOrg/actions/workflows/cd-ghpages.yml/badge.svg?branch=main)](https://github.com/MareMare/try-blazorwasm-standalone-singleOrg/actions/workflows/cd-ghpages.yml) | https://maremare.github.io/try-blazorwasm-standalone-singleOrg |
| [![Azure Static Web Apps CI/CD](https://github.com/MareMare/try-blazorwasm-standalone-singleOrg/actions/workflows/azure-static-web-apps-polite-moss-0d2a72510.yml/badge.svg?branch=main)](https://github.com/MareMare/try-blazorwasm-standalone-singleOrg/actions/workflows/azure-static-web-apps-polite-moss-0d2a72510.yml) | https://polite-moss-0d2a72510.2.azurestaticapps.net |
| ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?style=for-the-badge&logo=Cloudflare&logoColor=white) | https://try-blazorwasm-standalone-singleorg.pages.dev |

## `'Cannot read properties of undefined (reading 'toLowerCase')'`

GitHub Pages あるいは Azure Static Web Apps へ発行し、実際にサイトへアクセスすると次のエラーが表示される。
```
There was an error trying to log you in: 'Cannot read properties of undefined (reading 'toLowerCase')'
```

対処方法は *.csproj に以下を追加すれば良いらしい。
```xml

```

詳細は以下:
* https://github.com/dotnet/aspnetcore/issues/38082#issuecomment-1072762015

## `'"undefined" is not valid JSON'`
デプロイしたサイトへアクセスすると次のエラーが表示される。
```
There was an error trying to log you in: '"undefined" is not valid JSON'
```

対処方法は *.csproj に以下を追加すれば良いらしい。
```xml

```

詳細は以下:
* https://github.com/dotnet/aspnetcore/issues/44981#issue-1442616167
* https://github.com/dotnet/aspnetcore/issues/43293#issuecomment-1306046181

## リダイレクトの設定

詳細:

![](assets/AAD-redirect-setting.png)

## クライアントシークレットの設定
ユーザシークレットには以下のキーでクライアントシークレットを設定しているので、発行先の環境変数へ追加する必要がある。
```json
{
"AzureAD:ClientSecret": "<>"
}
```
### GitHub Pages

詳細:

![](assets/gh-pages-environment-secrets.png)

### Azure Static Web Apps

無料版ではダメだった:

![](assets/static-web-app-configuration.png)

![](assets/AADSTS900043.png)

[Azure AD 認証と承認のエラー コード \- Microsoft Entra \| Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/active-directory/develop/reference-aadsts-error-codes)

* AADSTS90043
> NationalCloudAuthCodeRedirection - 機能が無効になっています。

もしかして無料のホスティングプランだと設定できない?

![](assets/static-web-app-404.png)

以下が参考になりそう…

1. [Azure Static Web Apps の認証と承認 \| Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/static-web-apps/authentication-authorization?tabs=invitations) (マネージド認証)
> 1 構成済みの Azure Active Directory プロバイダーは、Microsoft アカウントでサインインを許可します。
>
> 特定の Active Directory テナントにログインを制限するには、[カスタム Azure Active Directory プロバイダー] を構成します。
2. [Azure Static Web Apps でのカスタム認証 \| Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/static-web-apps/authentication-custom?tabs=aad) (カスタム認証)
> カスタム認証は、Azure Static Web Apps Standard プランでのみ使用できます。
3. [Azure Static Web AppsのアプリにAzure ADカスタム認証機能を追加](https://itc-engineering-blog.netlify.app/blogs/azure-static-web-apps-auth)
> ## マネージド認証
> > Azure AD のどのテナントでもログインできてしまいます。

やりたいことは「カスタム認証」が適している。が、しかし無料が良かった…
ここで断念。

### Cloudflare Pages
[Deploy a Blazor Site · Cloudflare Pages docs](https://developers.cloudflare.com/pages/framework-guides/deploy-a-blazor-site/#creating-the-build-script)

詳細:

![](assets/cloudflare-pages-config.png)

* ビルドの構成
* ビルドコマンド
```sh
curl -sSL https://dot.net/v1/dotnet-install.sh > dotnet-install.sh;
chmod +x dotnet-install.sh;
./dotnet-install.sh -c 8.0 -InstallDir ./dotnet8;
./dotnet8/dotnet --version;
./dotnet8/dotnet workload install wasm-tools;
./dotnet8/dotnet publish "src/blazorwasm-standalone-singleOrg" -c Release -o output;
rm $(ls output/wwwroot/_framework/*.wasm);
```
* ビルド出力ディレクトリ
```sh
/output/wwwroot
```

## `Microsoft.Graph v4 to v5`
Microsoft.Graph Version="4.54.0" → Microsoft.Graph Version="5.1.0" は破壊的変更がある模様。

### 以下が参考になりそう:
* [Microsoft Graph \.NET SDK v5 changelog and upgrade guide](https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/feature/5.0/docs/upgrade-to-v5.md)
* [Upgrade 4\.50 sdk to 5\.0 \- Reference to type IAuthenticationProvider claims it is defined in Microsoft\.Graph\.Core, but cannot be found · Issue \#1695 · microsoftgraph/msgraph\-sdk\-dotnet](https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1695)
* https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1695#issuecomment-1464520102
* https://github.com/AzureAD/microsoft-identity-web/issues/2097
* https://github.com/microsoftgraph/msgraph-sample-aspnet-core/issues/84

### 一時的な解決方法?:
参考:

* https://github.com/AzureAD/microsoft-identity-web/issues/2097#issuecomment-1451707046
* https://gist.github.com/ashelopukho/5b00944c7744ebb4f9baa348e86f7e0e
* https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1695#issuecomment-1461759018
* https://github.com/svrooij/BlazorGraphExplorer/commit/ab989ff959883f43e7ead10ff4e3c506022dbf33

### 解決方法:
* [ASP\.NET Core Blazor WebAssembly で Graph API を使用する \| Microsoft Learn](https://learn.microsoft.com/ja-jp/aspnet/core/blazor/security/webassembly/graph-api?view=aspnetcore-8.0&pivots=graph-sdk-5)

GraphClientExtensions.cs:

```cs
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Graph;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
using IAccessTokenProvider = Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider;

///
/// Adds services and implements methods to use Microsoft Graph SDK.
///
internal static class GraphClientExtensions
{
public static IServiceCollection AddGraphClient(this IServiceCollection services, string? baseUrl, List? scopes)
{
if (string.IsNullOrEmpty(baseUrl) || scopes.IsNullOrEmpty())
{
return services;
}

services.Configure>(
options =>
{
scopes?.ForEach(scope =>
{
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
});
});

services.AddScoped();

services.AddScoped(
sp =>
new GraphServiceClient(
new HttpClient(),
sp.GetRequiredService(),
baseUrl));

return services;
}

///
/// Implements IAuthenticationProvider interface.
/// Tries to get an access token for Microsoft Graph.
///
private class GraphAuthenticationProvider : IAuthenticationProvider
{
private readonly IConfiguration _config;

public GraphAuthenticationProvider(IAccessTokenProvider tokenProvider, IConfiguration config)
{
this.TokenProvider = tokenProvider;
this._config = config;
}

public IAccessTokenProvider TokenProvider { get; }

public async Task AuthenticateRequestAsync(
RequestInformation request,
Dictionary? additionalAuthenticationContext = null,
CancellationToken cancellationToken = default)
{
var result = await this.TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
{
Scopes = this._config.GetSection("MicrosoftGraph:Scopes").Get(),
});

if (result.TryGetToken(out var token))
{
request.Headers.Add("Authorization", $"{CoreConstants.Headers.Bearer} {token.Value}");
}
}
}
}
```

Program.cs:

```cs
var baseUrl = builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"];
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes").Get>();
builder.Services.AddGraphClient(baseUrl, scopes);

```

appsettings.json:

```json
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": [ "user.read" ]
}
```

## Breaking changes Authentication in WebAssembly apps

詳細:

![image](https://user-images.githubusercontent.com/807378/226430419-706da0c0-3dd9-42c1-b12d-63cd50378182.png)
* https://github.com/dotnet/aspnetcore/issues/44973
* https://github.com/dotnet/AspNetCore.Docs/pull/27562
* https://github.com/dotnet/AspNetCore.Docs/blob/cfc5e4436eaeb090d3bfe55445285952aad7f07e/aspnetcore/blazor/security/includes/redirecttologin-component.md
* https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/7.0/wasm-app-authentication

```cs
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo(
$"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
}
}
```
👇
```cs
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject IOptionsSnapshot> Options
@code {
protected override void OnInitialized()
{
Navigation.NavigateToLogin(Options.Get(Microsoft.Extensions.Options.Options.DefaultName).AuthenticationPaths.LogInPath);
}
}
```

## Run Blazor Web Assembly locally
* [natemcmaster/dotnet\-serve: Simple command\-line HTTPS server for the \.NET Core CLI](https://github.com/natemcmaster/dotnet-serve)

```ps1
dotnet publish .\src\blazorwasm-standalone-singleOrg\ -c release -o output
rm output/wwwroot/_framework/Microsoft.Graph.wasm
dotnet serve -o -S -p:7117 -b -d:.\output\wwwroot
```

## Brotli Compression
* [ASP\.NET Core Blazor WebAssembly のホストと展開 \| Microsoft Learn](https://learn.microsoft.com/ja-jp/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-8.0#compression)
* `wwwroot\js\decode.js`/`wwwroot\js\decode.min.js`
* [google/brotli · GitHub](https://github.com/google/brotli/tree/master/js) から `decode.js` と `decode.min.js` をダウンロード
* `wwwroot\js` へコピー
* `*.csproj`
```xml

true



Always


```
* `wwwroot\index.html`
```html





import { BrotliDecode } from './js/decode.min.js';
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
if (type !== 'dotnetjs' && location.hostname !== 'localhost' && type !== 'configuration') {
return (async function () {
const response = await fetch(defaultUri + '.br', { cache: 'no-cache' });
if (!response.ok) {
throw new Error(response.statusText);
}
const originalResponseBuffer = await response.arrayBuffer();
const originalResponseArray = new Int8Array(originalResponseBuffer);
const decompressedResponseArray = BrotliDecode(originalResponseArray);
const contentType = type ===
'dotnetwasm' ? 'application/wasm' : 'application/octet-stream';
return new Response(decompressedResponseArray,
{ headers: { 'content-type': contentType } });
})();
}
}
});



```